BufferedInputStream and BufferedOutputStream

`BufferedInputStream` and `BufferedOutputStream` reduce the number of expensive underlying I/O operations by moving data through an in-memory byte buffer. They are useful when code performs many relatively small reads or writes against an underlying stream.

`BufferedInputStream` wraps an `InputStream` and keeps a block of bytes in memory. Calls such as `read()` can often be satisfied from that buffer instead of immediately accessing the underlying stream, reducing the number of low-level I/O operations.

`BufferedOutputStream` wraps an `OutputStream` and accumulates bytes in an in-memory buffer. Small writes are collected and later sent to the underlying stream as a larger block when the buffer fills, when `flush()` is called, or when the stream is closed.

Buffering improves efficiency, but it does not change the fundamental type of data being processed: these classes operate on bytes. They can wrap streams such as `FileInputStream` and `FileOutputStream`, and they can be combined with other stream decorators.

A buffered output stream must be flushed or closed when buffered data needs to reach the underlying destination. Closing a `BufferedOutputStream` also closes its underlying stream. Similarly, closing a `BufferedInputStream` releases its underlying stream.

Buffering is not a substitute for correct resource management. In modern Java, try-with-resources is the usual way to ensure streams are closed even when an exception occurs.

Key points

Interview questions

Does buffering change the data read from or written to a stream?

No. Buffering changes how data is transferred between the wrapper and the underlying stream, not the logical byte sequence exposed by the stream API. `BufferedInputStream` still provides byte reads, and `BufferedOutputStream` still accepts byte writes. The main purpose is to reduce the number of interactions with the underlying stream.

Why would you use `BufferedInputStream` instead of reading directly from a `FileInputStream`?

A `FileInputStream` can perform underlying I/O for individual read operations. `BufferedInputStream` reads a larger block into memory and then serves subsequent reads from that buffer when possible. This can substantially reduce the number of underlying I/O operations for workloads involving many small reads. The exact performance benefit depends on the workload and storage system.

What is the purpose of `flush()` on a `BufferedOutputStream`?

`BufferedOutputStream` may retain recently written bytes in its internal buffer instead of immediately passing them to the underlying stream. `flush()` requests that buffered data be written to the underlying output stream. This is useful when downstream consumers need the data before the stream is closed. Closing the stream also performs the necessary final flushing as part of closing the wrapper.

How should you manage buffered streams when an exception can occur?

Use try-with-resources so the stream is closed automatically when execution leaves the resource block. Closing the buffered stream also handles its pending buffered output before closing the underlying stream. This avoids resource leaks and makes cleanup reliable across normal and exceptional control flow. The same pattern applies to input and output streams.

What is the performance trade-off of buffering?

Buffering consumes some memory for the intermediate byte array, but it can reduce the overhead associated with frequent underlying I/O operations. The benefit is workload-dependent: sequential small operations often benefit more than workloads where the underlying API already performs effective buffering or where operations are already large. Buffer size also involves a trade-off between memory use and the frequency of refills or flushes. Performance should therefore be measured for the actual workload rather than assumed from buffering alone.