1. InputStreamReader class

Another interesting feature of streams is that you can combine multiple streams together into chains. A stream can read data not only from its internal data source, but also from another stream.

This is a very powerful mechanism in Java, which makes it possible to create complex data reading scenarios by connecting one stream to another. Such a scheme looks like this:

InputStreamReader class

When a program reads data from a data stream, the data stream in turn reads the data from its data source, which is another data stream or a file, for example.

What's more, each data stream not only reads in and gives out data, but can also transform it or perform various operations on it. A good example of such an "intermediate stream" is the InputStreamReader class.

We already know a class called FileReader — it is a Reader that reads data from a file. And where does InputStreamReader get its data from? That's right — from an InputStream.

When you create an InputStreamReader object, you need to pass in an InputStream object or one of its descendant classes. Example:

String src = "c:\\projects\\log.txt";
FileInputStream input = new FileInputStream(src);
InputStreamReader reader = new InputStreamReader(input);

The InputStreamReader class has all the methods that the Reader class has, and they work in exactly the same way.

The main difference between the InputStreamReader class and, say, FileReader is where they read data from. FileReader reads data from a file (duh — that's why it's called FileReader), but InputStreamReader reads data from an InputStream.

When you read a character from a FileReader object using the read() method, it in turn reads two bytes from the file on disk and returns them as chars.

When you read a character from an InputStreamReader object using the read() method, it in turn reads two bytes from the FileInputStream object passed to it, which in turn reads data from the file. The result is a chain of calls to read() methods.

2. BufferedReader class

Another interesting class that you are likely to use a lot is BufferedReader. This is also an "intermediate stream" that reads data from another stream.

As its name suggests, the BufferedReader class is a subclass of Reader and lets you read characters. But what is most interesting is that you also need to pass it a data source in the form of a stream from which characters can be read, i.e. a stream that inherits the Reader class.

What's the point? Unlike InputStreamReader, the BufferedReader class does not convert bytes to characters: it doesn't convert anything at all. Instead, it buffers data.

When a program reads a single character from a BufferedReader object, the object reads a large array of characters from its source stream all at once. And stores them internally.

When the next character is read from the BufferedReader object, it simply grabs the next character from its internal buffer array and returns it without accessing the data source. Only when all the characters in the buffer are used up does it read in another large array of characters.

The BufferedReader class also has a very useful method — String readLine(), which lets you read entire strings of data from the source stream all at once. You can use this method to, say, read a file and display its contents on the screen line by line. Example:

We specifically wrote some compact code to illustrate how convenient this can be. This code could also be written with a little more detail.

String src = "c:\\projects\\log.txt";

try(FileReader in = new FileReader(src);
BufferedReader reader = new BufferedReader(in))
   while (reader.ready())
      String line = reader.readLine();
Create a FileReader object. The data source is a file.
Create a BufferedReader object. The data source is a FileReader.
As long as there is still data in the reader
Read one line
Display the line
An important point:

If you chain together multiple streams, then the close() method only needs to be called on one of them. That stream will call the method on its data source, and so on, until close() is called on the final data stream.

3. Reading from the console

And one more interesting fact: the Scanner class is nothing more than an intermediate input stream that reads data from System.in, which is also a data stream.

Here are two ways to read a line from the console:

Scanner class BufferedReader and BufferedWriter classes
InputStream stream = System.in;
Scanner console = new Scanner(stream);
String line = console.nextLine();
InputStream stream = System.in;
InputStreamReader reader = new InputStreamReader(stream);
BufferedReader buff = new BufferedReader(reader);
String line = buff.readLine();

Our friend System.in is nothing more than a static in variable of the System class. It is an InputStream whose name is in.

So almost from the very beginning of your Java studies on CodeGym, you have been working with data streams and build chains from them. But now you will do it more consciously.