"Hello, Amigo! Today we'll get acquainted with input/output streams. We picked at this topic a couple of days ago, but today we will explore it thoroughly. Input/output streams are divided into 4 categories:"
1) Streams are divided according to their direction: input streams and output streams
2) Streams are divided according to their data type: those that work with bytes and those that work with characters.
Here these divisions are represented in a table:
|Input stream||Output stream|
|Works with bytes||InputStream||OutputStream|
|Works with characters||Reader||Writer|
If an object implements the InputStream interface, then it supports the ability to sequentially read bytes from it.
If an object implements the OutputStream interface, then it supports the ability to sequentially write bytes to it.
If an object implements the Reader interface, then it supports the ability to sequentially read characters (chars) from it.
If an object implements the Writer interface, then it supports the ability to sequentially write characters (chars) to it.
An output stream is like a printer. We can output documents to the printer. We can output data to an output stream.
For its part, an input stream can be compared to a scanner, or perhaps an electrical outlet. With a scanner, we can bring documents onto our computer. Or we can plug into an electrical outlet and receive electricity from it. We can receive data from an input stream.
"Where are they used?"
"These classes are used everywhere in Java. Our familiar friend System.in is a static InputStream variable named in in the System class."
"Seriously?! So all this time I've been using an InputStream and didn't even realize it. Is System.out also a stream?"
"Yes, System.out is a static PrintStream (a descendant of OutputStream) variable in the System class."
"You mean to tell me that I've always been using streams and didn't even know it?"
"Yes, and that just tells us how convenient these streams are. You just grab one and use it."
"But you can't say that about System.in. We constantly had to add BufferedReader or InputStreamReader to it."
"That's true. But there were also reasons for that."
There are a lot of data types, and many ways to work with them. So the number of standard I/O classes grew very quickly, though they did everything in almost the same way. To avoid this complexity, Java developers used the principle of abstraction and divided classes into many small parts.
But you can connect these parts in a coherent way and get very complex functionality, if you need it. Look at this example:
|Output a string to the console|
|Store the console output stream in a separate variable.
Output a string to the stream.
|Create a dynamic (expanding) byte array in memory.
Connect it to a new output stream (PrintStream object).
Output a string to the stream.
"Honestly, this is like a Lego set. Only it's not clear to me what any of this code is doing."
"Don't worry about that for now. Everything in its own due time."
This is what I want you to remember: If a class implements the OutputStream interface, you can write bytes to it. Almost exactly like you output data to the console. What it does with it is its business. With our "Lego kit", we don't care about the purpose of each individual part. We care about the fact that the large selection of parts lets us build such cool things.
"Okay. Then where do we start?"