— Привіт, Аміго! Сьогодні ми будемо знайомитися з  потоками введення-виводу. Кілька днів тому ми трохи чіпляли цю тему, але сьогодні пройдемося по ній ґрунтовно. Потоки введення-виводу поділяються на 4 категорії:

1) Потоки діляться за напрямом: потоки введення і потоки виведення

2) Потоки діляться за типом даних: працюють з байтами або працюють з символами.

Таблиця:

Потік введення Потік виведення
Працює з байтами InputStream OutputStream
Працює із символами Reader Writer

Якщо об'єкт реалізує інтерфейс InputStream, то він підтримує послідовне читання з нього байт (byte).

Якщо об'єкт реалізує інтерфейс OutputStream, значить, він підтримує послідовний запис до нього байт (byte).

Якщо об'єкт реалізує інтерфейс Reader, то він підтримує послідовне читання з нього символів (char).

Якщо об'єкт реалізує інтерфейс Writer, то він підтримує послідовний запис до нього символів (char).

Потоки введення/виводу - 1

Потік виводу нагадує принтер. Ми можемо виводити документи на принтер. У потік виведення ми можемо виводити дані.

Тоді потік введення можна порівняти зі сканером, або з розеткою. За допомогою сканера ми можемо ввести документи до комп'ютера. Також ми можемо підключитися до розетки та отримувати з неї електрику. З потоку введення ми можемо отримувати дані.

— А де вони використовуються?

— Ці класи використовують у Java повсюдно. Відомий нам System.in – це статична змінна на ім'я in типу InputStream у класі System.

— Треба ж! Виявляється, весь цей час я скористався потоком InputStream і не знав про це. System.out – теж потік?

— Так, System.out – це статична змінна на ім'я out типу PrintStream (спадкоємець OutputStream) у класі System.

— Тобто. я весь час користувався потоками і навіть не підозрював це?

— Так, і це свідчить лише про те, наскільки такі потоки зручні. Просто береш і користуєшся.

— Хоча цього не можна сказати про System.in. До нього постійно доводилося додавати BufferedReader та InputStreamReader.

— Так це так. Але на це також були свої причини.

Як бачиш, типів даних дуже багато, як і способів роботи з ними. Тому кількість стандартних класів введення-виводу дуже швидко зростала, хоч вони й робили майже те саме. Щоб уникнути такої складності, розробники Java застосували принцип абстракції та розділили класи на багато дрібних частин.

Зате їх можна з'єднати послідовно та отримати дуже складну функціональність, якщо вона тобі знадобилася. Дивись приклад:

Виведення рядка на консоль
System.out.println("Hello");
Зберегли потік виведення на консоль в окрему змінну.
Виводимо рядок у потік.
PrintStream console = System.out;
console.println("Hello");
Створили динамічний масив байт, що розтягується, в пам'яті.
Зв'язали його з новим потоком виведення – об'єктів PrintStream
Виводимо рядок у потік.
ByteArrayOutputStream stream = new ByteArrayOutputStream();
PrintStream console = New PrintStream(stream);
console.println("Hello");

— Справді, чимось нагадує конструктор Lego. Тільки незрозуміло, що цей код робить.

— Нехай тебе це не турбує зараз. Усьому свій час.

Хочу, щоб ти запам'ятав що: якщо клас реалізує інтерфейс OutputStream – він дозволяє записувати в нього байти. Майже так, як ти виводиш дані на консоль. Що він робитиме з цими даними – його завдання. У «конструкторі» важливим є не призначення окремого елемента, а наскільки класні речі ми можемо зібрати завдяки різноманіттю існуючих елементів.

— Добре. Тоді з чого ми розпочнемо?