Skip to main content

Buffered Streams

By default, standard streams like FileInputStream and FileWriter execute I/O operations directly through the operating system. This means every read or write request is handled directly by the disk or network, which is extremely inefficient and slow.

To resolve this performance bottleneck, Java provides Buffered Streams. These streams use an in-memory buffer (typically 8KB) to store data temporarily, minimizing the number of direct system calls.


How Buffering Works

Instead of requesting data from the disk byte-by-byte:

  1. Reading: The buffered stream reads a large block of data from the disk at once and stores it in an internal buffer. Subsequent read operations pull directly from this memory buffer. Once the buffer is empty, another block is fetched.
  2. Writing: The buffered stream accumulates written data in its memory buffer. Once the buffer is full (or when the stream is flushed/closed), the entire chunk is written to the disk in a single operating system operation.

Buffered Streams Classes

Java wraps existing streams to add buffering capabilities using the decorator design pattern.

Stream TypeUnbuffered ClassBuffered Class Wrapper
Byte InputInputStreamBufferedInputStream
Byte OutputOutputStreamBufferedOutputStream
Char InputReaderBufferedReader
Char OutputWriterBufferedWriter

Working with Buffered Streams

Wrapping Streams

You wrap an unbuffered stream by passing it to the constructor of the buffered stream:

// Wrapping a Character Stream
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));

// Wrapping a Byte Stream
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.bin"));

[!NOTE] When you close a wrapper/decorator stream (like BufferedReader), it automatically calls the close() method of the underlying stream (FileReader). You do not need to close both.

Useful Methods in BufferedReader & BufferedWriter

  • String readLine() (BufferedReader): Reads a full line of text. Returns the line as a String (excluding the line termination characters \n or \r\n), or null if the end of the stream is reached.
  • void newLine() (BufferedWriter): Writes a platform-specific line separator (e.g. \n on Unix, \r\n on Windows).
  • void flush() (BufferedWriter): Forces any buffered data to be written immediately to the target.

Practical Code Example: Line-by-Line File Copy

Here is a complete program demonstrating how to read a text file line-by-line using BufferedReader and write it to another file using BufferedWriter.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedCopyDemo {
public static void main(String[] args) {
String sourceFile = "large_input.txt";
String destFile = "large_output.txt";

// Create temporary sample file for the demo
try (FileWriter setupWriter = new FileWriter(sourceFile)) {
setupWriter.write("Line 1: Welcome to Java Buffered Streams.\n");
setupWriter.write("Line 2: Buffering is highly optimized for performance.\n");
setupWriter.write("Line 3: End of the file.");
} catch (IOException e) {
e.printStackTrace();
}

// Run the buffered copy operation
try (BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(destFile))) {

String line;
int lineCount = 0;

// readLine() reads until a newline character or end of stream
while ((line = reader.readLine()) != null) {
lineCount++;
// Write the line along with a platform-appropriate newline
writer.write("Processed " + line);
writer.newLine();
}

// Flush ensures all data is written to disk from the buffer
writer.flush();
System.out.println("Finished copying. Total lines copied: " + lineCount);

} catch (IOException e) {
System.err.println("I/O error during copying: " + e.getMessage());
e.printStackTrace();
}
}
}