JavaRush /Курси /Java Syntax Zero /Потоки для вводу даних

Потоки для вводу даних

Java Syntax Zero
Рівень 16, Лекція 2
Відкрита

1. Потоки даних

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

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

Процеси обміну даними можна розділити на два типи: отримання даних і надсилання даних. Наприклад, ви зчитуєте дані з клавіатури за допомогою об'єкта Scanner — це отримання даних. А потім виводите дані на екран за допомогою команди System.out.println() — це надсилання даних.

Для опису процесу обміну даними в програмуванні використовується термін «потік». Чому саме така назва?

У реальному житті може бути потік води або потік людей (людський потік). А потік у програмуванні — це потік даних.

Потоки — це універсальний інструмент. Вони дають програмі змогу отримувати дані звідки завгодно (вхідні потоки) і надсилати дані куди завгодно (вихідні потоки). Потоки поділяються на два види:

  • Вхідний потік (Input): використовується для отримання даних
  • Вихідний потік (Output): використовується для надсилання даних

Щоб потоки можна було «помацати руками», розробники Java створили два класи: InputStream і OutputStream.

Клас InputStream має метод read(), який дає змогу читати дані з вхідного потоку. А клас OutputStream має метод write(), який дає змогу записувати дані у вихідний потік. Ці класи мають також інші методи, але про них поговоримо трохи згодом.

Байтові потоки

Що ж це за дані та в якому вигляді їх можна читати? Інакше кажучи, які типи даних підтримуються цими класами?

Ці класи універсальні й тому підтримують найпоширеніший тип даних — byte. В об'єкт OutputStream можна записувати байти (і масиви байтів), а з об'єкта InputStream можна читати байти (або масиви байтів). Це й усе — ніякі інші типи даних не підтримуються.

Тому такі потоки ще називають байтовими потоками.

Потоки мають таку особливість, що дані з них можна читати (або в них писати) тільки послідовно. Ви не можете прочитати дані із середини потоку, не прочитавши всі попередні дані.

Саме так працює читання з клавіатури з використанням класу Scanner: ви читаєте дані з клавіатури послідовно — рядок за рядком. Прочитали один рядок, прочитали наступний рядок, прочитали наступний рядок і т. д. Саме тому метод читання рядка називається nextLine() (дослівно — «наступний рядок»).

Запис даних у потік OutputStream теж відбувається послідовно. Наочним прикладом є виведення на екран. Ви виводите один рядок, за ним іще один, потім іще один — це послідовне виведення. Ви не можете вивести перший рядок, потім десятий, а потім другий. Усі дані записуються в потік виводу тільки послідовно.

Символьні потоки

На попередніх рівнях ви дізналися, що рядки — це другий за популярністю тип даних, і це справді так. Дуже багато інформації передається у вигляді символів і цілих рядків. Комп'ютер чудово передавав би все у вигляді байтів, але людям це не дуже зручно.

Java-програмісти врахували цей факт і написали ще два класи: Reader і Writer. Клас Reader — це аналог класу InputStream, тільки його метод read() читає не байти, а символи — char. Клас Writer відповідає класу OutputStream і так само, як і клас Reader, працює із символами (char), а не з байтами.

Якщо порівняти ці чотири класи, отримаємо таку картину:

Байти (byte) Символи (char)
Читання даних
InputStream
Reader
Запис даних
OutputStream
Writer

Практичне застосування

Класи InputStream, OutputStream, Reader і Writer в явному вигляді ніхто не використовує: їх не приєднано до зовнішніх об'єктів, з яких можна читати дані або в які можна писати дані. Проте ці чотири класи мають чимало класів-спадкоємців, які дуже багато вміють.


2. Клас InputStream

Клас InputStream цікавий тим, що є батьківським класом для сотень класів-спадкоємців. У ньому самому немає ніяких даних, проте він має методи, які передає всім своїм класам-спадкоємцям.

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

Методи класу InputStream і всіх його класів-спадкоємців:

Методи Опис
int read()
Читає один байт із потоку
int read(byte[] buffer)
Читає масив байтів із потоку
byte[] readAllBytes()
Читає всі байти з потоку
long skip(long n)
Пропускає n байтів у потоці (читає та відкидає)
int available()
Перевіряє, скільки ще байтів залишилося в потоці
void close()
Закриває потік

Побіжно перегляньмо ці методи:

Метод read()

Метод read() читає один байт із потоку та повертає його. Вас може спантеличити тип результату — int, проте так було зроблено тому, що тип int є стандартним типом усіх цілих чисел. Три перші байти чисел типу int дорівнюватимуть нулю.

Метод read(byte[] buffer)

Це ще одна модифікація методу read(). Цей метод дає змогу зчитати з InputStream одразу масив байтів. Масив для збереження байтів потрібно передати як параметр. Метод повертає число — кількість реально прочитаних байтів.

Припустімо, ви читаєте дані з файлу за допомогою класу FileInputStream у буфер розміром 10 кілобайт. Якщо довжина файлу лише 2 кілобайти, всі дані буде записано в масив-буфер, а метод поверне число 2048 (2 кілобайти).

Метод readAllBytes()

Це дуже хороший метод. Він просто читає всі дані з InputStream, доки вони не закінчаться, і повертає їх у вигляді цілісного масиву байтів. Дуже зручний для читання невеликих файлів. Великі файли можуть фізично не поміститися в пам'яті, і метод кине виняток.

Метод skip(long n)

Цей метод дає змогу пропустити n перших байтів з об'єкта InputStream. Оскільки дані читаються строго послідовно, цей метод просто читає n перших байтів із потоку і відкидає їх.

Повертає число байтів, які було реально пропущено (якщо потік закінчився раніше, ніж було перебрано n байтів).

Метод int available()

Метод повертає решту байтів у потоці.

Метод void close()

Метод close() закриває потік даних і вивільняє пов'язані з ним зовнішні ресурси. Після закриття потоку читати з нього дані більше не можна.

Напишімо для прикладу програму, що копіює дуже великий файл. Його неможливо цілком прочитати в пам'ять за допомогою методу readAllBytes(). Приклад:

Код Примітка
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStream для читання з файлу
OutputStream для запису у файл

Буфер, в який зчитуються дані
Доки в потоці є дані

Зчитуємо дані в буфер
Записуємо дані з буфера в другий потік

У цьому прикладі ми використали два класи: для читання даних з файлу — клас FileInputStream, спадкоємець класу InputStream, а для запису даних у файл — клас FileOutputStream, спадкоємець класу OutputStream. Про другий клас розповімо трохи згодом.

Ще одна цікавинка — це змінна real. Коли з файлу буде прочитано останній блок даних, може виявитися, що його довжина менша за 64 Кб. Тому в output потрібно теж записати не весь буфер, а тільки його частину: перші real байтів. Саме це й робить метод write().


16
Задача
Java Syntax Zero, 16 рівень, 2 лекція
Недоступна
Переплутані байти
Переплутані байти
16
Задача
Java Syntax Zero, 16 рівень, 2 лекція
Недоступна
Чомусь не копіюється...
Чомусь не копіюється...

3. Клас Reader

Клас Reader — це повний аналог класу InputStream, тільки він працює не з байтами, а із символами — char. Клас Reader, так само, як і клас InputStream, сам по собі ніде не використовується: він є батьківським класом для сотень класів-спадкоємців, яким передає всі спільні методи.

Методи класу Reader (і всіх його класів-спадкоємців):

Методи Опис
int read()
Читає один символ char з потоку
int read(char[] buffer)
Читає масив символів char з потоку
long skip(long n)
Пропускає n символів char в потоці (читає та відкидає)
boolean ready()
Перевіряє, чи залишилися ще символи в потоці
void close()
Закриває потік

Методи дуже схожі на методи класу InputStream, хоча є незначні відмінності.

Метод int read()

Цей метод читає з потоку один символ char і повертає його. Тип char розширюється до типу int, але перші два байти результату завжди нульові.

Метод int read(char[] buffer)

Це ще одна модифікація методу read(). Цей метод дає змогу зчитати з Reader відразу масив символів. Масив для збереження символів потрібно передати як параметр. Метод повертає число — кількість реально прочитаних символів.

Метод skip(long n)

Цей метод дає змогу пропустити n перших символів з об'єкта Reader. Він працює так само, як аналогічний метод класу InputStream. Повертає число символів, які були реально пропущені.

Метод boolean ready()

Повертає true, якщо в потоці ще є непрочитані символи.

Метод void close()

Метод close() закриває потік даних і вивільняє пов'язані з ним зовнішні ресурси. Після закриття потоку читати з нього дані більше не можна.

Для порівняння напишімо програму, що копіює текстовий файл:

Код Примітка
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Reader для читання з файлу
Writer для запису у файл

Буфер, в який зчитуються дані
Доки в потоці є дані

Зчитуємо дані в буфер
Записуємо дані з буфера в другий потік

16
Задача
Java Syntax Zero, 16 рівень, 2 лекція
Недоступна
Фейсконтроль
Фейсконтроль
Коментарі (25)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Ігор Рівень 117
13 жовтня 2025
Під час роботи з цим Рівнем №24, НЕ ВИРІШУЙТЕ ЗАДАЧІ ОДРАЗУ, прочитайте весь рівень, тут всього 5 лекцій, а лише потім поверніться до практики - майже всі задачі зроблені на матеріалах майбутніх лекцій, тобто віднесіться до задач як до сторінки з практикою на завершення рівня. Збережете купу нервів :)
RMykola Рівень 20
9 березня 2025
Це дуже обурливо. Цілу лекцію даємо матреріал. Але в задачі ви його не можете використовувати. Можна використовувати лише матеріал, який ми ще не проходили. Прекрасно.
Alexander Kisil Рівень 51
26 жовтня 2024
Фейсконтроль задача - це щось!! (в нехорошому сенсі) 1) При використанні replaceAll - "Задача не компілюється на сервері" (хоча я звик, що у мене щось не компвлюється) 2) Якщо в задачі стоіть послідовність точка-кома-пробіл , то тільки в такій послідовності перевірок проходить перевірку. Трохи не зручно, коли такі моменти забирають купу часу.
Кирило Рівень 43
9 серпня 2024
Це точно перша лекція рівня!?)
Андрій Рівень 18
3 квітня 2024
Вирішив не паритись і в останній задачі просто використав метод replaceAll)
Olexandr Рівень 5
21 січня 2024
Ладно hard задача, з якою можно розібратися. Але вимагати застосувати лямбда вирази, які ми навіть близько не вчили - це сильно!🤬 Я розумію і підтримую, коли потрібно самому щось знайти і загуглити. Але для цієі задачі я маю сам вивчити всю Джаву, чи як???
oleh ronin Рівень 47
6 листопада 2023
Напишу це ще раз, люди не розуміють..🙂 Агов народ,те що в лекціях трапляється матеріал який ще не вчили, таким чином JavaRush привчає вас гуглити інформацію яка вам портібна. Так уже сталось що повністю вивчити мову Java неможливо. Правильно поставлене питання по проблемі в гугл - це вже 70% вирішення проблеми. Так що не "плюйтесь" тут що ми такого не вчили, і "JavaRush зкатились", тут нам на благо))
Dmytrii Рівень 69
6 серпня 2024
то ще все прийде з опитом, але нам, нубам, потрібну інфу треба давати в лекціях
Олег Рівень 65
8 січня 2025
Олег, мушу з Вами не погодитись, та поясню чому. я плачу гроші ДжаваРашу не за те, щоб Вони мене в гуглі навчили ставити питання, а за знання, які я отримую. Так що не пудрите мозок наступним учням.
Yurii Kuzniak Рівень 30
7 червня 2023
Перепрошую, але люди добрі, шо це, як це вирішити, шо таке +=2, ви шо здуріли те все мені зараз пояснювати, а дещо навіть не пояснювати, дуже важка тема як на мене, не знаю як вам
hidden #3421731 Рівень 28
27 квітня 2024
не зрозумів питання .int a += 2; це те ж саме що і int a = a + 2;
Elder_HD Рівень 42 Expert
4 червня 2023
Тільки дякуючи задачам, рівень яких не відповідає викладеному матеріалу, вдалося нормально розібратися в темі (в т.ч. побайтовому читанні вхідного потоку). Задачі не без гуглінга, але все же вдалось вирішити. Про лямба вираз у 3й задачі я взагалі мовчу))) (хоч і алгоритм застосував той самий). Всім, хто зіштовхується з труднощами при виконанні задач, рекомендую читати та аналізувати методи, які використовуються або вимагаються у задачах (read(), read(byte[] bytes), write (byte[] bytes), readAllBytes() і тд). Особливо важливо знати "що саме повертає метод". До речі, в процесі вирішення задач стало цікаво, який з методів побайтвого читання при копіюванні ефективніший: - read() - побайтове копіювання. Зчитали один байт, один байт записали - read(byte[] buffer) - побайтове копіювання обмеженим "буфером". Зчитали певну кількість даних (що помістилася у буфер), скопіювали, зчитали наступну порцію, скопіювали і тд... - readAllBytes() - копіювання цілісним масивом байтів, який був зчитаний з вхідного потоку. Зручно, але викине помилку, якщо не вистачить пам'яті (вхідний файл завеликий).
222.1Balaban Danil Рівень 32
21 квітня 2023
Перед задачою з рівнем Hard, рекомендую прочитати цю лекцію https://javarush.com/groups/posts/2275-files-path
Serhii #2958832 Рівень 51 Expert
7 червня 2023
Дякую. Дійсно, без інформації з даної лекції не зробив би не тільки task1504 (Hard), а task1506 також. Не пам'ятаю, щоб клас Files з його readAllLines раніше проходили.