Java BufferedReader
›Short versionOpen if you only have a minute.
- BufferedReader reads characters from another Reader, a chunk at a time.
- The charset sits on the reader underneath the buffer, not on the buffer itself.
- readLine() drops the line ending. It returns null at the end, and "" for a blank line.
- Closing the BufferedReader closes the reader and the byte stream under it.
- try-with-resources on that outer reader releases the file, including when the body throws.
A is the character stream you use when the input is text and you want lines. The is a char array in front of another Reader. The file, if there is one, is still a byte stream under that reader, decoded with a .
Name the charset, then read lines
Files.newBufferedReader opens the path, decodes with the charset you pass, and wraps a buffer. UTF-8 is the charset to pass for a file that should read the same on every machine. The one-argument form also uses UTF-8. An older FileReader with no charset uses the JVM default instead.
Path path = Path.of("input.txt");
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} returns the characters and drops \n, \r\n, or \r. The is null. A blank line is "", because a terminator was found and nothing preceded it. The last line of a file is returned even when the file does not end with a terminator. The next call is the one that returns null.
The same stack written out by hand makes the layers visible. Bytes, then decoding, then the buffer.
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("input.txt"), StandardCharsets.UTF_8))) {
for (String line; (line = reader.readLine()) != null; ) {
System.out.println(line);
}
}What the buffer changes
Each readLine does not have to mean a separate read of the file. The buffer refills when it runs out. That is why a line loop over a large log stays cheap, as long as each line itself fits in memory.
This buffer is on the way in, so it does not delay errors the way a BufferedWriter does. A writer can accept a short write, return, and throw only when flush or close pushes the array. A reader either has the characters or it is waiting on the source. When you pair the two, the reader will not see the writer's latest line until that flush. An empty file, read in that gap, is the writer's buffer still holding the line.
Who closes
should name the BufferedReader and nothing under it. close() on that reader closes the InputStreamReader, which closes the FileInputStream. The descriptor is released by the byte stream at the bottom. Closing the byte stream first, while the buffer still expects to read, pulls the file out from under the reader.
The close also runs when the loop throws, for example on a line that Integer.parseInt cannot accept. The file does not stay open until the stack frame is abandoned. If both the body and close throw, the body exception is the one that propagates, and the close exception is suppressed on it.
readLine() just returned an empty string. Is the file finished?
No. An empty string is a line with a terminator and no characters. The file is finished when a later readLine() returns null. A last line that has characters and no trailing newline is returned as those characters; the call after that is null.