Buffered Input and Output

Basic Java
≈ 2 min read

Why add another layer around a stream

Frequent access to a file or another external source can be noticeably more expensive than working with memory. Buffered streams reduce the number of small operations: they read or accumulate data in blocks inside an internal buffer while user code keeps working with a familiar API.

For bytes, Java provides BufferedInputStream and BufferedOutputStream; for characters, it provides BufferedReader and BufferedWriter. They usually wrap an existing stream or reader/writer.

try (InputStream in = new BufferedInputStream(
        new FileInputStream("photo.jpg"))) {
    byte[] header = in.readNBytes(16);
}

From the outside, this is still an InputStream, so the rest of the code does not need to know whether a buffer exists inside. This is a good example of stream composition: an extra capability is added by a wrapper.

BufferedReader and reading lines

For text, buffering is especially visible through BufferedReader: it can read data line by line with readLine(). The returned string does not contain the line terminator, and null means the end of the data.

try (BufferedReader reader = new BufferedReader(
        new FileReader("app.log", StandardCharsets.UTF_8))) {
    String line = reader.readLine();
    System.out.println(line);
}

When flush() is needed

Buffered output may keep written data in memory for some time. The flush() method asks the stream to pass accumulated data farther down the chain without closing the stream itself. This matters when another participant must see the output before the resource finishes its work.

try (BufferedWriter writer = new BufferedWriter(
        new FileWriter("status.txt", StandardCharsets.UTF_8))) {
    writer.write("READY");
    writer.flush();
}
Do not call flush() after every small write without a reason. Forcing a flush too often reduces the benefit of buffering. During normal stream closing, remaining data is passed on automatically.