Java BufferedReader
Jakob Jenkov |
The Java BufferedReader class, java.io.BufferedReader
, provides buffering for your
Java Reader
instances. Buffering can speed up IO quite a bit. Rather than read one character at a time from the
underlying Reader
, the Java BufferedReader
reads a larger block (array) at a time. This is typically much faster,
especially for disk access and larger data amounts.
The Java BufferedReader
is similar to the BufferedInputStream
but they are not exactly the same. The main difference between BufferedReader
and
BufferedInputStream
is that BufferedReader
reads characters (text), whereas the
BufferedInputStream
reads raw bytes.
The Java BufferedReader
class is a subclass of the Java Reader class,
so you can use a BufferedReader
anywhere a Reader
is required.
Java BufferedReader Example
To add buffering to a Java Reader
instance, simply wrap it in a BufferedReader
.
Here is how that looks:
BufferedReader bufferedReader = new BufferedReader( new FileReader("c:\\data\\input-file.txt"));
This example creates a BufferedReader
which wraps a FileReader
. The
BufferedReader
will read a block of characters from the FileReader
(typically into a
char
array). Each character returned from read()
is thus returned from this internal
array. When the array is fully read the BufferedReader
reads a new block of data into the array etc.
BufferedReader Buffer Size
You can set the buffer size to use internally by the BufferedReader
.
You provide the size as a constructor parameter, like this:
int bufferSize = 8 * 1024; BufferedReader bufferedReader = new BufferedReader( new FileReader("c:\\data\\input-file.txt"), bufferSize );
This example sets the internal buffer to 8 KB. It is best to use buffer sizes that are multiples of 1024 bytes. That works best with most built-in buffering in hard disks etc.
Except for adding buffering to Reader
instances, a Java BufferedReader
behaves pretty much
like a Reader
. The BufferedReader
has one extra method though, the readLine()
method. This
method can be handy if you need to read input one line at a time. Here is a BufferedReader
readLine()
example:
String line = bufferedReader.readLine();
The readLine()
method will return a textual line (all text until at line break is found) read from
the BufferedReader
. If there is no more data to read from the underlying Reader
,
then the BufferedReader
's readLine()
method will return null
.
Read Characters From a BufferedReader
The read()
method of a Java BufferedReader
returns an int which contains the char
value of the next
character read. If the read()
method returns -1, there is no more data to read in the BufferedReader
,
and it can be closed. That is, -1 as int value, not -1 as byte or char value. There is a difference here!
Here is an example of reading all characters from a Java BufferedReader
:
Reader reader = new BufferedReader( new FileReader("/path/to/file/thefile.txt")); int theCharNum = reader.read(); while(theCharNum != -1) { char theChar = (char) theCharNum; System.out.print(theChar); theCharNum = reader.read(); }
Notice how the code example first reads a single character from the Java BufferedReader
and checks if
the char numerical value is equal to -1. If not, it processes that char
and continues reading
until -1 is returned from the BufferedReader
read()
method.
As mentioned earlier, the BufferedReader
will actually read an array of characters from the underlying
Reader
and return these characters one by one, rather than forwarding every read()
call
to the underlying Reader
. When all characters in the internal buffer have been read, the
BufferedReader
attempts to refill the buffer again, until no more characters can be read from the
underlying Reader
.
Read Array of Characters From BufferedReader
The Java BufferedReader
class also has a read()
method that takes a char
array
as parameter, as well as a start offset and length. The char
array is where the read()
method will read the characters into. The offset parameter is where in the char
array the read()
method should start reading into. The length parameter is how many characters the read()
method
should read into the char
array from the offset and forward. Here is an example of reading
an array of characters into a char
array with a Java BufferedReader
:
Reader reader = new BufferedReader( new FileReader("/path/to/file/thefile.txt")); char[] theChars = new char[128]; int charsRead = reader.read(theChars, 0, theChars.length); while(charsRead != -1) { System.out.println(new String(theChars, 0, charsRead)); charsRead = reader.read(theChars, 0, theChars.length); }
The read(char[], offset, length)
method returns the number of characters read into the
char
array, or -1 if there are no more characters to read in the BufferedReader
, for instance
if the end of the file the BufferedReader
is connected to has been reached.
Read a Line From BufferedReader
The Java BufferedReader
has a special read method named readLine()
which reads a
full line of text from the BufferedReader
's internal buffer. The readLine()
method
returns a String
. If there are no more lines to read from the BufferedReader
,
the readLine()
method returns null
. Here is an example of reading the lines of
a text file one by one using a Java BufferedReader
:
BufferedReader bufferedReader = new BufferedReader( new FileReader("/path/to/file/thefile.txt")); String line = bufferedReader.readLine(); while(line != null) { System.out.println(line); line = bufferedReader.readLine(); }
Read Performance
Reading an array of characters at a time is faster than reading a single character at a time from a
Java Reader
. However, since the BufferedReader
does some internal buffering already,
the difference is most likely not as dramatic as with a Reader
that uses no buffering.
You will most likely still see a small difference though.
Skip Characters
The Java BufferedReader
class has a method named skip()
which can be used to skip over
a number of characters in the input that you do not want to read. You pass the number of characters to
skip as parameter to the skip()
method. Here is an example of skipping characters from
a Java BufferedReader
:
long charsSkipped = bufferedReader.skip(24);
This example tells the Java BufferedReader
to skip over the next 24 characters in the BufferedReader
.
The skip()
method returns the actual number of characters skipped. In most cases that will be
the same number as you requested skipped, but in case there are less characters left in the BufferedReader
than the number you request skipped, the returned number of skipped characters can be less than the number
of characters you requested skipped.
Closing a BufferedReader
When you are finished reading characters from the BufferedReader
you should remember to close it.
Closing a BufferedReader
will also close the Reader
instance from which the
BufferedReader
is reading.
Closing a BufferedReader
is done by calling its close()
method. Here is how
closing a BufferedReader
looks:
bufferedReader.close();
You can also use the try-with-resources construct
introduced in Java 7. Here is how to use and close a BufferedReader
looks with the try-with-resources
construct:
Reader reader = new FileReader("data/data.bin"); try(BufferedReader bufferedReader = new BufferedReader(reader)){ String line = bufferedReader.readLine(); while(line != null) { //do something with line line = bufferedReader.readLine(); } }
Notice how there is no longer any explicit close()
method call. The try-with-resources construct
takes care of that.
Notice also that the first FileReader
instance is not created inside
the try-with-resources block. That means that the try-with-resources block will not automatically close this
FileReader
instance. However, when the BufferedReader
is closed
it will also close the Reader
instance it reads from, so the FileReader
instance will get closed when the BufferedReader
is closed.
Reusable BufferedReader
One of the weaknesses of the standard Java BufferedReader
is that it can only be used once.
Once you close it, it's no longer usable. If you need to read a lot of files, or network streams, you have to create
a new BufferedReader for each file or network stream you want to read. This means that you are creating a
new object, and more importantly, a new char array which is used as buffer inside the BufferedReader.
This can put pressure on the Java garbage collector, if the number of files or streams read is high, and if they
are read quickly after each other.
An alternative is to create a reusable BufferedReader where you can replace the underlying source
Reader
, so the BufferedReader and its internal byte array buffer can be reused. To save
you the trouble, I have created such a ReusableBufferedReader
, and included the code for it
further down this tutorial. First, I want to show you how using this ReusableBufferedReader
looks.
Create a ReusableBufferedReader
First you need to create a ReusableBufferedReader
. Here is an example of how to create a
ReusableBufferedReader
:
ReusableBufferedReader reusableBufferedReader = new ReusableBufferedReader(new char[1024 * 1024]);
This example creates a ReusableBufferedReader
with a 2 MB char array (1024 * 1024 chars, 1 char = 2 bytes)
as its internal buffer.
Set Source
When you have created a ReusableBufferedReader
you need to set the Reader
on it to use as underlying data source. Here is how you set a source Reader
on a
ReusableBufferedReader
:
FileReader reader = new FileReader("/mydata/somefile.txt"); reusableBufferedReader.setSource(reader);
The setSource()
method actually returns a reference to the ReusableBufferedReader
,
so you can actually create a ReusableBufferedReader
and set the source in a single instruction:
ReusableBufferedReader reusableBufferedReader = new ReusableBufferedReader(new byte[1024 * 1024]) .setSource(new FileReader("/mydata/somefile.txt"));
Reusing a ReusableBufferedReader
When you are done using the ReusableBufferedReader
you need to close it. Closing it will only
close the underlying source Reader
. After closing a ReusableBufferedReader
you can use it again, simply by setting a new source Reader
on it. Here is how it looks to
reuse a ReusableBufferedReader
:
reusableBufferedReader.setSource(new FileReader("/mydata/file-1.txt")); //read data from ReusableBufferedReader reusableBufferedReader.close(); reusableBufferedReader.setSource(new FileReader("/mydata/file-1.txt")); //read data from ReusableBufferedReader reusableBufferedReader.close();
ReusableBufferedReader Code
Here is the code for the ReusableBufferedReader
described above. Note, that this implementation
only overrides the read()
and read(char[] dest, int offset, int length)
methods of
the Reader
class that it extends. The rest of the
Reader
methods have been left out to keep the code shorter - but you can implement them yourself
in case you need them.
import java.io.IOException; import java.io.Reader; public class ReusableBufferedReader extends Reader { private char[] buffer = null; private int writeIndex = 0; private int readIndex = 0; private boolean endOfReaderReached = false; private Reader source = null; public ReusableBufferedReader(char[] buffer) { this.buffer = buffer; } public ReusableBufferedReader setSource(Reader source){ this.source = source; this.writeIndex = 0; this.readIndex = 0; this.endOfReaderReached = false; return this; } @Override public int read() throws IOException { if(endOfReaderReached) { return -1; } if(readIndex == writeIndex) { if(writeIndex == buffer.length) { this.writeIndex = 0; this.readIndex = 0; } //data should be read into buffer. int bytesRead = readCharsIntoBuffer(); while(bytesRead == 0) { //continue until you actually get some bytes ! bytesRead = readCharsIntoBuffer(); } //if no more data could be read in, return -1; if(bytesRead == -1) { return -1; } } return 65535 & this.buffer[readIndex++]; } @Override public int read(char[] dest, int offset, int length) throws IOException { int charsRead = 0; int data = 0; while(data != -1 && charsRead < length){ data = read(); if(data == -1) { endOfReaderReached = true; if(charsRead == 0){ return -1; } return charsRead; } dest[offset + charsRead] = (char) (65535 & data); charsRead++; } return charsRead; } private int readCharsIntoBuffer() throws IOException { int charsRead = this.source.read(this.buffer, this.writeIndex, this.buffer.length - this.writeIndex); writeIndex += charsRead; return charsRead; } @Override public void close() throws IOException { this.source.close(); } }
Tweet | |
Jakob Jenkov |