 Java, Spring and Web Development tutorials  1. Overview
Java utilizes input streams as an abstraction for data input/output (I/O) operations. We can use streams with various data sources such as files, memory, or a network. As the ‘stream’ in the name suggests, the data flow is one-directional, as with a pipe. This is the most efficient way of using a stream. However, sometimes we need to go back in the stream.
In this tutorial, we’ll learn how to mark a position in the stream so that we can return to it later.
The abstract InputStream class provides a basic API. We have three functions to manage the stream behavior:
- mark() – marks the position in the stream. It takes the read-ahead limit, which indicates how many subsequent bytes we can read in and safely return
- reset() – causes a return to the position immediately after the marked one
- markSupported() – returns true if a particular stream supports the mark and reset operations
From the InputStream class, we can get the default implementation of these functions. Let’s check them:
- markSupported() – returns false
- reset() – throws an IOException
- mark() – has no effect
Any class that extends InputStream will behave in the same way, unless it implements its own version of these functions.
We use the ByteArrayInputStream class to read from memory. It supports resetting, so its markSupported() returns true. Therefore, let’s see a simple example of how mark() and reset() work together:
@Test
void givenByteArrayInputStream_whenMarkAndReset_thenReadMarkedPosition() {
final int EXPECTED_NUMBER = 3;
byte[] buffer = { 1, 2, 3, 4, 5 };
ByteArrayInputStream bis = new ByteArrayInputStream(buffer);
int number = bis.read(); //get 1
number = bis.read(); //get 2
bis.mark(0); //irrelevant for ByteArrayInputStream
number = bis.read(); //get 3
number = bis.read(); //get 4
bis.reset();
number = bis.read(); //should get 3
assertEquals(EXPECTED_NUMBER, number);
}
As we call mark() after the second read, we scheduled a reset to the very next, third position. Then, we actually do it after two additional read operations.
Notably, for ByteArrayInputStream, the read-ahead limit is meaningless. It’s because once the stream is created, all data is available. We emphasized it by passing zero to mark().
To jump back when reading from a file, let’s use the BufferedInputStream class. We can regard this class as a container for the FileInputStream object:
@Test
void givenBufferedInputStream_whenMarkAndReset_thenReadMarkedPosition() throws IOException {
final int readLimit = 500;
final char EXPECTED_CHAR = 'w';
FileInputStream fis = new FileInputStream(fileName);
//content:
//All work and no play makes Jack a dull boy
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read(); // A
bis.read(); // l
bis.read(); // l
bis.read(); // space
bis.mark(readLimit); // at w
bis.read();
bis.read();
bis.reset();
char test = (char) bis.read();
assertEquals(EXPECTED_CHAR, test);
}
Notably, we passed the instance of FileInputStream to the BufferedInputStream constructor. Moreover, this stream implementation demands a meaningful read-ahead limit passed to mark().
5. Mark Position Invalidation
When we read too many bytes, the mark position becomes invalid, and reset() fails:
@Test
void givenBufferedInputStream_whenMarkIsInvalid_thenIOException() throws IOException {
final int bufferSize = 2;
final int readLimit = 1;
assertThrows(IOException.class, () -> {
FileInputStream fis = new FileInputStream(fileName);
// constructor accepting buffer size
BufferedInputStream bis = new BufferedInputStream(fis, bufferSize);
bis.read();
bis.mark(readLimit);
bis.read();
bis.read();
bis.read(); // this read exceeds both read limit and buffer size
bis.reset(); // mark position is invalid
});
}
In this example, we used the BufferedInputStream constructor, which accepts the initial size of the inner buffer. When both the read-ahead limit and buffer size were exceeded, the call to reset() threw the IOException.
6. RandomAccessFile Alternative
With the RandomAccessFile class, we can obtain random access to a file in read or write mode. So, we can freely choose a position for the operation on the file. This way is clearly different from the concept of streams, which assumes one-directional processing.
Let’s emulate the work of a stream’s mark()/reset() pair with RandomAccessFile functions:
@Test
void givenRandomAccessFile_whenSeek_thenMoveToIndicatedPosition() throws IOException {
final char EXPECTED_CHAR = 'w';
RandomAccessFile raf = new RandomAccessFile(fileName, "r"); //open file in read mode
//content:
//All work and no play makes Jack a dull boy
raf.read(); // A
raf.read(); // l
raf.read(); // l
raf.read(); // space
long filePointer = raf.getFilePointer(); //at w
raf.read();
raf.read();
raf.read();
raf.read();
raf.seek(filePointer);
int test = raf.read();
assertEquals(test, EXPECTED_CHAR);
}
We used the getFilePointer() function, combined with the seek() one. getFilePointer() returns the current offset in the file, while seek() sets the offset to the provided value.
7. Conclusion
In this article, we learned how to move back when reading a file. First, we focused on the InputStream class and its descendants. We checked if a particular stream supported resetting. Then, we demonstrated how the resetting worked. Finally, as an alternative to the stream approach, we looked at a RandomAccessFile class that enabled random access to a file.
As always, the code for the examples is available over on GitHub. The post How to Reset InputStream and Read File Again first appeared on Baeldung.
Content mobilized by FeedBlitz RSS Services, the premium FeedBurner alternative. |