User Professor Hans Noodles
Professor Hans Noodles
Level 41

Reading from the keyboard: "readers"

Published in the Java Developer group
Hi! The lessons and tasks in Level 3 taught you how to display stuff on the console, and, moving in the other direction, how to read data from the keyboard.
Reading from the keyboard: "readers"  - 1
You even learned to use the following complex construct to accomplish this:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
But there's one question we haven't answered yet.

How in the world does this work?

In reality, programs are rarely entirely independent. They communicate with other programs, systems, the Internet, etc. By "communicate", we mainly mean "exchange data". That is, they receive some external data and also send internal program data somewhere. Examples of programs exchanging data abound in everyday life. For example, many websites let you sign in using your Facebook or Twitter account instead of registering. In this situation, two programs (e.g. Twitter and the website you're signing in to) exchange the necessary data. The final result is that you are successfully signed in. The word "stream" is used to describe the process of data exchange. Where did this name come from? In your experience, a "stream" may be more associated with rivers and than with programming. That's no accident :) A stream is, in essence, a moving piece of data. In other words, in programming, it isn't water that flows — but rather data in the form of bytes and characters. We can receive bits of data from a data stream and then use them. Again, we'll use the water/flow analogy: you can scoop water from a river to make soup, put out a fire, or water your flowers. Streams let you work with any data source: whether the Internet, your computer's file system, or something else — it makes no difference. Streams are a universal tool. They allow a program to receive data from anywhere (input streams) and send it anywhere (output streams). Their task is the same: to take data from one place and send it to another. There are two types of streams:
  1. Input streams are used to receive data
  2. Output streams are for sending data.
In Java, these streams are implemented by the InputStream and OutputStream classes. But the streams can be categorized another way. In addition to input and output streams, we also speak of byte streams and character streams. The meaning here should be clear enough: byte stream sends information as a set of bytes, while a character stream sends it as a set of characters. In this lesson, we'll dwell on input streams. I'll put a link with information about output streams at the end of the lesson. You can read it on your own :) Now take a look at this code:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
When going through the lessons, didn't you think this line was fairly intimidating? :) That won't be the case once we've explored how it works. Let's put things right. We'll start at the end. System.in is an InputStream object, an instance of the class we spoke about early. It is an input stream linked to a system input device (the keyboard). By the way, you're indirectly familiar with this stream. After all, you frequently use its "coworker" — System.out! System.out is the system output stream. It is used to output data to the console via your favorite method System.out.println(), which you use constantly :) System.out is a stream for sending data to the console, while System.in is for getting data from the keyboard. It's all simple :) What's more, we can read data from the keyboard without this huge construct. We can simply write: System.in.read();

public class Main {

   public static void main(String[] args) throws IOException {

       while (true) {
           int x = System.in.read();
           System.out.println(x);
       }
   }
}
The InputStream class (remember, System.in is an InputStream object) has a read() method that lets you read data. There's one problem: it reads bytes, not characters. It is boring to use English letters only so let's try reading the Chinese character "魚" from the keyboard (just copy this letter from here and paste it to console using ctrl + v on PC or Command + v on Mac). This character means 'a fish' by the way. Console output: 233 173 154 10 This symbol and many other Chinese occupy 3 bytes in the computer's memory (unlike Latin letters, which occupy just 1 byte). In this case, 4 bytes are read from the stream: the first three represent the character "魚", and other byte represents a new line (Enter). Accordingly, System.in in its unadorned form is not an option for us. Humans (with rare exceptions!) don't know how to read bytes. But the InputStreamReader class comes to the rescue! Let's see what kind of animal this is.

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
We pass System.in to the InputStreamReader object. The class name says it! We create an InputStreamReader object and pass it an input stream that it will read data from. In this case...

new InputStreamReader(System.in)
...we tell it, "you will read data from the system input stream (from the keyboard)". But this isn't its only function! The InputStreamReader doesn't only receive data from the stream. It also converts byte streams to character streams. In other words, you no longer need to convert the data from "ones and zeros" to a "human-readable language". InputStreamreader does everything for you. Of course, InputStreamReader isn't limited to reading data from the console. It can read data from other places, too. For example, from a file:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

   public static void main(String[] args) throws IOException {
       InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("C:\\Users\\username\\Desktop\\testFile.txt"));
   }
}
Here we create a FileInputStream (one flavor of InputStream), pass in the file path, and pass the stream itself to the InputStreamReader. Now it will be able to read data from the file (if a file actually exists at the path, of course). We also use the InputStreamReader class's read() method to read data (the source of the data doesn't matter: the console, a file, or somewhere else). What's the difference between System.in.read() and InputStreamReader.read()?\ Let's again try to read the character "魚" with an InputStreamReader. I remind you of what was actually read by System.in.read(): 233 173 154 10 And how does the InputStreamReader do the same work?

public class Main {

   public static void main(String[] args) throws IOException {

       InputStreamReader reader = new InputStreamReader(System.in);
       while (true) {
           int x = reader.read();
           System.out.println(x);
       }
   }
}
Console output: 39770 10 The difference is immediately apparent. The last byte (representing the new line) remains unchanged (the number 10), but the character "魚" was converted into a single code "39770". This is what it means to read characters! If you don't believe that 39770 represents the letter "魚", it's easy to convince yourself :)
import java.io.IOException;

public class Main {

   public static void main(String[] args) throws IOException {

       char x = 39770;
       System.out.println(x);
   }
}
Console output: But if InputStreamReader is so great, why do we also need BufferedReader? InputStreamReader knows how to read data and convert bytes into characters. What more could we ask for? Why another Reader? :/ The answer is very simple: for greater performance and convenience. Let's start with performance. When BufferedReader reads data, it uses a special area called a buffer, where it "stores" the characters it reads. Ultimately, when these characters are needed in the program, they will be taken from the buffer, not directly from the data source (keyboard, file, etc.). This saves a lot of resources. To understand how this works, imagine a courier in a large company. The courier sits in an office, waiting for someone to bring packages for delivery. Each time he receives a new package, he can immediately hit the road. But there could be lots of packages during the day. He would have to make lots of trips between the office and the delivery addresses. Instead, the courier puts a box in his office. Everyone puts their packages into the box. Now the courier can calmly take the box and move from address to address. This saves a lot of time, because he doesn't have to return to the office every time. In this example, the box is just a buffer, and the office is a data source. It's much easier for the courier to take packages from a single box when making deliveries than to go back to the office every time. He'll save gasoline, too. Similarly, in a program it's much less resource-intensive to take data from a buffer than to refer to the data source each time. As a result, BufferedReader+InputStreamReader is faster than InputStreamReader alone. We've considered performance. What about convenience? The main advantage is that Bufferedreader can read data not only one character at a time (though it can do this with its read() method), but also whole lines at a time! This is done using the readLine() method;

public class Main {

   public static void main(String[] args) throws IOException {

       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String s = reader.readLine();
       System.out.println("We read this line from the keyboard:");
       System.out.println(s);
   }
}
Console output: CodeGym is the best website for learning Java! We read this line from the keyboard: CodeGym is the best website for learning Java! This is especially useful when reading large amounts of data. Reading one or two lines of text character by character is still feasible. But reading in "War and Peace" one letter at a time would be somewhat problematic :)
Comments (110)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Anonymous #10790765 Level 4, India, India
22 August 2021
Great explanation. This was quite helpful.
Cairnstech Level 9, Newbury
18 June 2021
This was very helpful. Thank you!
sachin Level 6, Singapore, Singapore
18 June 2021
another article on input and output stream https://codegym.cc/groups/posts/245-inputoutput-in-java-fileinputstream-fileoutputstream-and-bufferedinputstream-classes
sachin Level 6, Singapore, Singapore
18 June 2021
very well explained.
Kesares Level 13, Germany
4 April 2021
Where is the link about output streams?
TaoLu Level 20, 泾县, China
12 March 2021
how about this:

        new Thread(() -> {
            InputStreamReader reader = new InputStreamReader(System.in);
            int i = 10;
            while (i-- > 0) {
                int x = 0;
                try {
                    x = reader.read();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println(x);
            }
        }).start();

        int i = 10;
        while (i-- > 0) {
            int x = System.in.read();
            System.out.println(x);
        }
i don't know why, is the System.in disturbed
lichennan Level 8, China
11 March 2021
我想学英语了...
kyrie Level 20, Jacksonville, Canada
10 March 2021
我的英文不太好,看不太懂,机器翻译有点生硬不好理解。
Andy Lee Level 10
22 February 2021
clear
P.B.Kalyan Krishna Level 16, Guntur, India
12 February 2021
Beautiful explanation! The best I had seen so far.