Buffered I/O in Java
Introduction
In Java, Input/Output (I/O) operations involve reading data from a source (like a file or network) or writing data to a destination.
By default, standard streams like FileReader or FileInputStream interact directly with the Operating System (OS). This means that every single byte or character read/written triggers a system call to the disk or network. Because hardware interactions are magnitudes slower than CPU operations, unbuffered I/O is notoriously slow.
Java solves this performance bottleneck using Buffered Streams.
How Buffering Works
Buffered I/O classes act as a "wrapper" around standard I/O classes. Instead of reading one byte from the disk at a time, a buffered stream reads a large chunk of data (usually 8 KB) into a chunk of memory (the "buffer" array) all at once.
Subsequent reads are served directly from this fast memory buffer. When the buffer is empty, another large chunk is fetched from the disk. This reduces the number of slow system calls from thousands down to a handful.
Key Buffered Classes
1. Character Streams (For Text Files)
Used when reading/writing human-readable text (String, char).
BufferedReader: Wraps aReader(likeFileReader). Includes the highly usefulreadLine()method.BufferedWriter: Wraps aWriter. Includes thenewLine()method for OS-independent line breaks.
2. Byte Streams (For Binary Files)
Used for raw binary data (images, audio, zip files).
BufferedInputStream: Wraps anInputStream.BufferedOutputStream: Wraps anOutputStream.
Code Example: Reading a File efficiently
Always wrap your file readers in a BufferedReader for performance. Notice the use of the try-with-resources block, which automatically closes the streams, preventing memory leaks.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReadExample {
public static void main(String[] args) {
String filePath = "data.txt";
// 1. Create a FileReader
// 2. Wrap it inside a BufferedReader
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
// Read line by line until the end of the file (returns null)
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Error reading the file: " + e.getMessage());
}
}
}
Code Example: Writing with BufferedWriter
When writing data, the BufferedWriter stores your text in memory. It only actually writes to the disk when the buffer is full, or when you explicitly call flush(), or when the stream is close()'d.
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriteExample {
public static void main(String[] args) {
String[] lines = {"First line", "Second line", "Third line"};
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
for (String line : lines) {
bw.write(line);
bw.newLine(); // Adds OS-specific newline (\n or \r\n)
}
// bw.flush(); // Automatically called by try-with-resources on close
} catch (IOException e) {
e.printStackTrace();
}
}
}
Interview Q&A
Q: Why do we need flush()?
A: When using buffered output, data is held in RAM until the buffer fills up. If the program crashes before the buffer is full, that data is lost and never hits the disk. flush() forces the buffer to write its current contents to the destination immediately, regardless of whether it's full. (Note: Using try-with-resources automatically flushes and closes the stream).