1. Клас InputStreamReader

Ще однією цікавою особливістю потоків є можливість об'єднувати кілька потоків у ланцюжки. Потік може читати дані не тільки із джерела даних, в якому вони зберігаються, але також з іншого потоку.

Це дуже потужний механізм у Java, який дав змогу конструювати складні сценарії читання даних, поєднуючи різні потоки. Ця схема має приблизно такий вигляд:

Коли програма читає дані з потоку даних, він своєю чергою читає їх зі свого джерела даних: з іншого потоку даних або, наприклад, із файлу.

Кожен потік даних не просто читає та віддає дані, а водночас може перетворювати їх або виконувати з ними різні операції. Наочним прикладом такого «проміжного потоку» даних є клас InputStreamReader.

Ми вже знаємо клас із назвою FileReader — це Reader, який читає дані з файлу. А звідки читає дані клас InputStreamReader? Правильно: з потоку InputStream.

Щоб створити об'єкт типу InputStreamReader, у нього потрібно передати об'єкт типу InputStream або об'єкт його класу-спадкоємця. Приклад:

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

Клас InputStreamReader має всі методи, які має клас Reader, і працюють вони так само.

Основна відмінність класу InputStreamReader і, скажімо, класу FileReader — це джерело, звідки вони читають дані: FileReader читає дані з файлу (тому він і називається FileReader), а InputStreamReader читає дані з потоку InputStream.

Коли ви читаєте символ з об'єкта FileReader за допомогою методу read(), він своєю чергою читає з файлу на диску два байти й повертає їх вам як char.

Коли ви читаєте символ з об'єкта InputStreamReader за допомогою методу read(), він своєю чергою читає два байти з переданого в нього об'єкта FileInputStream, який своєю чергою читає дані з файлу. У підсумку маємо ланцюжок викликів методів read().


2. Клас BufferedReader

Є ще один цікавий клас, який ви, ймовірно, будете часто використовувати — BufferedReader. Це теж «проміжний потік», який читає дані з іншого потоку.

Клас BufferedReader, як випливає з його назви, є класом-спадкоємцем класу Reader і дає змогу читати символи. Однак найцікавіше те, що джерелом даних для нього теж є потік, з якого можна читати символи, — потік-спадкоємець класу Reader.

У чому ж сенс? На відміну від Inputstreamreader, клас BufferedReader не перетворює байти на символи: він взагалі нічого не перетворює. Натомість він буферизує дані.

Коли програма читає з об'єкта BufferedReader один символ, він читає зі свого потоку-джерела відразу великий масив символів і зберігає їх у своєму внутрішньому буфері.

Під час читання наступного символу з об'єкта BufferedReader цей символ можна просто взяти з цього внутрішнього масиву-буфера, не звертаючись до потоку-джерела даних. І тільки тоді, коли буде прочитано всі символи в буфері, він знову прочитає великий масив символів.

А ще клас BufferedReader має дуже корисний метод String readLine(), який дає змогу читати дані з потоку-джерела відразу рядками. За допомогою цього методу можна, наприклад, прочитати якийсь файл і вивести його вміст на екран рядок за рядком. Приклад:

Ми спеціально навели код у компактному форматі, щоб показати, як це зручно. Цей код можна також написати детальніше.

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

try(FileReader in = new FileReader(src);
BufferedReader reader = new BufferedReader(in))
{
   while (reader.ready())
   {
      String line = reader.readLine();
      System.out.println(line);
   }
}


Створюємо об'єкт FileReader, джерело даних — файл
Створюємо об'єкт BufferedReader, джерело даних — об'єкт FileReader
Доки в reader є дані

Прочитати один рядок
Вивести рядок на екран
Важливий момент:

Якщо ви з'єднали кілька потоків у ланцюжок, метод close() досить викликати тільки у одного з них: він викличе його у свого джерела даних і т. д., доки не буде закрито останній потік даних.



3. Читання з консолі

Ще один цікавий факт: клас Scanner — це, власне, вхідний проміжний потік даних, який читає їх з іншого потоку даних — System.in.

Наведемо два способи читання рядка з консолі:

Клас Scanner Класи BufferedReader і InputStreamReader
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();

Виявляється, наш знаменитий System.in — це статична змінна in класу System. Її тип — InputStream, а ім'я — in.

Тож ви працюєте з потоками даних і будуєте з них ланцюжки практично із самого початку вивчення Java на JavaRush. А відтепер ви зможете це робити більш осмислено.