Buffering in Java
BufferedReader/Writer, why unbuffered I/O is slow, flush.
One trip per item
Imagine carrying groceries from the car one item per trip. That's unbuffered I/O: each read() or write() can become a separate, expensive call into the operating system. A buffer is a shopping bag: it moves data in big chunks (8 KB by default) and serves small reads and writes from memory.
Wrapping streams
Wrap a stream to buffer it: new BufferedReader(reader), BufferedWriter, BufferedInputStream… **BufferedReader also adds readLine()** (returns null at the end) and **lines()**.
try (var in = Files.newBufferedReader(path)) {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
}Counting lines
What does this print?
var br = new BufferedReader(
new StringReader("x\n\ny\n"));
System.out.println(br.lines().count());234
Show the answer
3 — the lines are "x", "" and "y". The empty line in the middle counts, but the final \n doesn't create an extra empty line.
Writers hold your data
A **BufferedWriter collects written text in memory and sends it only when the buffer fills, or when you call flush() or close()**. That's the whole point of buffering — and its catch.
Is it there yet?
What does this print?
void main() throws IOException {
var sw = new StringWriter();
var bw = new BufferedWriter(sw);
bw.write("hey");
System.out.println("[" + sw + "]");
bw.flush();
System.out.println("[" + sw + "]");
}[hey] [hey][] [hey][] []
Show the answer
[] — "hey" is still waiting in the BufferedWriter's buffer, so the StringWriter is empty. After **flush()** it arrives: [hey].
The empty log file
The program ends while "started" is still in the buffer, so the file stays empty. **close() flushes automatically — use try-with-resources** so it always happens.
// bug: never flushed or closed
var w = new BufferedWriter(
new FileWriter("log.txt"));
w.write("started");
try (var w2 = new BufferedWriter(
new FileWriter("log.txt"))) {
w2.write("started");
} // close() flushesBuffering in production
Reading a big file through an unbuffered stream can be orders of magnitude slower than buffered — a classic performance fix in code reviews. And lost final log lines or truncated CSV exports usually trace back to a writer that was never flushed or closed.
Key takeaways
- Wrap streams: new BufferedReader(reader)
- BufferedReader adds readLine() and lines()
- Written data stays in memory until flush/close
- close() flushes automatically — use try-with-resources
BufferedReader's default buffer is 8,192 characters — you can pass a different size to its constructor if you need to.
Practice questions
Why is reading a file one byte at a time through an unbuffered FileInputStream slow?
- Java decodes every byte as UTF-8
- Each read() can be a separate, expensive call into the operating system
- The file is reopened for every byte
- Bytes are read from the end of the file backwards
Check your answer
Each read() can be a separate, expensive call into the operating system. System calls have a large fixed cost. A BufferedInputStream fetches thousands of bytes per call and hands them out from memory.
What does this print?
var br = new BufferedReader(
new StringReader("one\ntwo\n"));
System.out.println(br.lines().count());- 1
- 2
- 3
- 8
Check your answer
2. lines() splits on line terminators and doesn't produce an extra empty line for the final \n, so there are 2 lines.