BufferedReader and BufferedWriter

BufferedReader and BufferedWriter are buffered character streams used for efficient text I/O in Java. They reduce the cost of repeated underlying I/O operations and provide convenient methods such as `readLine()` and `newLine()`.

`BufferedReader` reads characters from another `Reader` through an internal buffer. Instead of requiring the underlying reader to handle every small read separately, it can obtain a larger block and serve subsequent character requests from that buffer.

`BufferedWriter` provides the corresponding buffering layer for output. Characters written to it are accumulated in memory and passed to the underlying `Writer` in larger operations, reducing the number of underlying writes.

A common backend use case is processing a text file line by line. `BufferedReader.readLine()` returns one line at a time without its line-termination characters, while `BufferedWriter.write()` can write text and `newLine()` can insert the platform's line separator.

Buffering does not replace the underlying stream; it wraps it. For example, `new BufferedReader(new FileReader("input.txt"))` adds buffering to a character-based file reader. Similarly, a `BufferedWriter` can wrap a `FileWriter` or another compatible `Writer`.

When writing, remember to close or flush the `BufferedWriter`. Closing the buffered wrapper also closes its underlying writer, while `flush()` pushes buffered characters downstream without closing the resource. Try-with-resources is generally the preferred way to manage these resources.

Key points

Interview questions

Does BufferedReader improve performance for every kind of input?

Buffering is particularly useful when the underlying source would otherwise be accessed frequently for small reads. It can reduce the overhead associated with those repeated operations. However, the actual benefit depends on the underlying source and access pattern, and buffering is not a guarantee of a specific speedup. It also does not change the semantics of the underlying character data.

Why would you use a BufferedReader instead of a FileReader directly?

`BufferedReader` adds an internal character buffer around another `Reader`, so repeated small reads can be served from memory rather than repeatedly accessing the underlying source. It also provides `readLine()`, which is useful when processing text line by line. This usually makes text-file processing both more convenient and more efficient.

What is the difference between BufferedReader and BufferedWriter?

`BufferedReader` is used for buffered character input, while `BufferedWriter` is used for buffered character output. The reader exposes methods such as `read()` and `readLine()`, while the writer exposes methods such as `write()` and `newLine()`. Both wrap another character-based I/O object and use buffering to reduce the frequency of underlying I/O operations.

Why is try-with-resources useful with BufferedReader and BufferedWriter?

These classes hold resources that should be closed when processing is finished. Try-with-resources automatically closes objects that implement `AutoCloseable`, even when an exception occurs. Closing a buffered writer also flushes its remaining buffered data and closes the underlying writer. This reduces the risk of resource leaks and incomplete output.

How would you process a large text file without loading the entire file into memory?

A common approach is to wrap the file reader in a `BufferedReader` and process the result one line at a time. `readLine()` retrieves the next line, and the loop continues until it returns `null`. This keeps the application from constructing a representation of the entire file in memory at once. For output, a `BufferedWriter` can similarly write results incrementally.