How do an InputStream, InputStreamReader and BufferedReader work together in Java?

This Streams in Java concepts and usage link, give a very nice explanations.

This

Streams, Readers, Writers, BufferedReader, BufferedWriter – these are the terminologies you will deal with in Java. There are the classes provided in Java to operate with input and output. It is really worth to know how these are related and how it is used. This post will explore the Streams in Java and other related classes in detail. So let us start:

Let us define each of these in high level then dig deeper.

Streams
Used to deal with byte level data

Reader/Writer
Used to deal with character level. It supports various character encoding also.

BufferedReader/BufferedWriter
To increase performance. Data to be read will be buffered in to memory for quick access.

While these are for taking input, just the corresponding classes exists for output as well. For example, if there is an InputStream that is meant to read stream of byte, and OutputStream will help in writing stream of bytes.

InputStreams
There are many types of InputStreams java provides. Each connect to distinct data sources such as byte array, File etc.

For example FileInputStream connects to a file data source and could be used to read bytes from a File. While ByteArrayInputStream could be used to treat byte array as input stream.

OutputStream
This helps in writing bytes to a data source. For almost every InputStream there is a corresponding OutputStream, wherever it makes sense.


UPDATE

What is Buffered Stream?

Here I'm quoting from Buffered Streams, Java documentation (With a technical explanation):

Buffered Streams

Most of the examples we've seen so far use unbuffered I/O. This means each read or write request is handled directly by the underlying OS. This can make a program much less efficient, since each such request often triggers disk access, network activity, or some other operation that is relatively expensive.

To reduce this kind of overhead, the Java platform implements buffered I/O streams. Buffered input streams read data from a memory area known as a buffer; the native input API is called only when the buffer is empty. Similarly, buffered output streams write data to a buffer, and the native output API is called only when the buffer is full.

Sometimes I'm losing my hair reading a technical documentation. So, here I quote the more humane explanation from https://yfain.github.io/Java4Kids/:

In general, disk access is much slower than the processing performed in memory; that’s why it’s not a good idea to access the disk a thousand times to read a file of 1,000 bytes. To minimize the number of times the disk is accessed, Java provides buffers, which serve as reservoirs of data.

enter image description here

In reading File with FileInputStream then BufferedInputStream, the class BufferedInputStream works as a middleman between FileInputStream and the file itself. It reads a big chunk of bytes from a file into memory (a buffer) in one shot, and the FileInputStream object then reads single bytes from there, which are fast memory-to-memory operations. BufferedOutputStream works similarly with the class FileOutputStream.

The main idea here is to minimize disk access. Buffered streams are not changing the type of the original streams — they just make reading more efficient. A program performs stream chaining (or stream piping) to connect streams, just as pipes are connected in plumbing.


  • InputStream, OutputStream, byte[], ByteBuffer are for binary data.
  • Reader, Writer, String, char are for text, internally Unicode, so that all scripts in the world may be combined (say Greek and Arabic).

  • InputStreamReader and OutputStreamWriter form a bridge between both. If you have some InputStream and know that its bytes is actually text in some encoding, Charset, then you can wrap the InputStream:

    try (InputStreamReader reader =
            new InputStreamReader(stream, StandardCharsets.UTF_8)) {
         ... read text ...
    }
    

There is a constructor without Charset, but that is not portable, as it uses the default platform encoding.

On Android StandardCharset may not exist, use "UTF-8".

The derived classes FileInputStream and BufferedReader add something to the parent InputStream resp. Reader.

A FileInputStream is for input from a File, and BufferedReader uses a memory buffer, so the actual physical reading does not does not read character wise (inefficient). With new BufferedReader(otherReader) you add buffering to your original reader.

All this understood, there is the utility class Files with methods like newBufferedReader(Path, Charset) which add additional brevity.