Вітання! У лекціях та завданнях ми навчабося виводити дані в консоль, і навпаки – зчитувати дані з клавіатури. Ти навіть навчився використовувати для цього складну конструкцію:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Але на одне запитання ми так і не відповіли.
А як це взагалі працює?
Насправді будь-яка програма найчастіше існує не сама по собі. Вона може спілкуватися з іншими програмами, системами, інтернетом та ін. Під словом "спілкуватися" ми насамперед подразнюємося "обмінюватися даними". Тобто приймати якісь дані ззовні, а власні дані навпаки, кудись відправляти. Прикладів обміну даними між програмами багато навіть у повсякденному житті. Так, на багатьох сайтах ти можеш замість реєстрації авторизуватися за допомогою свого облікового запису в Facebook або Twitter. У цій ситуації дві програми, скажімо, Twitter та сайт, на якому ти намагаєшся зареєструватися, обмінюються необхідними даними між собою, після чого ти бачиш кінцевий результат – успішну авторизацію. Для опису процесу обміну даними у програмуванні часто використовується термін “ потік”. Звідки взагалі взялася така назва? Потік більше асоціюється з річкою або струмком, ніж з програмуванням. Насправді, це недарма :) Потік - це, по суті, шматок даних, що переміщається. Тобто в програмуванні потоком “тече” не вода, а дані у вигляді байтів і символів. З потоку даних ми можемо отримувати дані частинами і щось із нею робити. Знову ж таки, застосуємо "водно-плинну" аналогію: з річки можна зачерпнути води, щоб зварити суп, згасити пожежу або полити квіти. За допомогою потоків ти можеш працювати з будь-якими джерелами даних: інтернет, файлова система твого комп'ютера або ще щось — без різниці. Потоки – інструмент універсальний. Вони дозволяють програмі отримувати дані звідусіль (вхідні потоки) і відправляти їх будь-куди (вихідні). Їхнє завдання одне - брати дані в одному місці і відправляти в інше. Потоки поділяються на два види:- Вхідний потік ( Input ) - використовується для прийому даних
- Вихідний потік ( Output ) - для надсилання даних.
InputStream
, вихідний - в класі OutputStream
. Але є й інший спосіб поділу потоків. Вони діляться як на вхідні і вихідні, але й байтові і символьні . Тут сенс зрозумілий і пояснень: байтовий потік передає інформацію як набору байт, а символьний — як набору символів. У цій лекції ми докладно зупинимося на вхідних потоках. А інформацію про вихідні я докладу посилання наприкінці, і ти зможеш прочитати про це сам:) Отже, наш код:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Ти, мабуть, ще під час читання лекцій подумав, що це виглядає досить жахливо? :) Але це тільки доти, доки ми не розібралися, як ця штука працює. Зараз виправимо! Почнемо з кінця. System.in
- Це об'єкт класу InputStream
, про який ми говорабо на початку. Це вхідний потік, і він прив'язаний до системного введення даних - клавіатурі. Ви з ним, до речі, опосередковано знайомі. Адже ти часто використовуєш у роботі його "колегу" - System.out
! System.out
— це системний потік виведення даних, він використовується для виведення на консоль у тому самому методі System.out.println()
, яким ти постійно користуєшся:) System.out
— потік для надсилання даних на консоль, аSystem.in
— щоб отримати дані з клавіатури. Все просто:) Більше того: щоб рахувати дані з клавіатури, ми можемо обійтися без цієї великої конструкції та написати просто: 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);
}
}
}
У класі InputStream
(а System.in
, нагадаю, є об'єктом класу InputStream
) є метод read()
, який дозволяє зчитувати дані. Одна проблема: він зчитує байти , а не символи . Спробуємо рахувати з клавіатури російську букву "Я". Виведення в консоль:
Я
208
175
10
Російські літери займають у пам'яті комп'ютера 2 байти (на відміну від англійських, які займають лише 1). В даному випадку з потоку вважалося 3 байти: два перші позначають нашу літеру "Я", і ще один - перенесення рядка (Enter). Тому варіант використовувати голий System.in
нам не підійде. Людина (за рідкісними винятками!) не вміє читати байти. Тут нам на допомогу і приходить наступний клас - InputStreamReader
! Давай розберемося, що то за звір такий.
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
Ми передаємо потік System.in
об'єкту InputStreamReader
. Загалом, якщо перевести його назву російською, все виглядає очевидно - "зчитувач вхідних потоків". Власне, саме для цього він і потрібний! Ми створюємо об'єкт класу InputStreamReader
та передаємо йому вхідний потік, з якого він повинен зчитувати дані. В даному випадку...
new InputStreamReader(System.in)
...ми говоримо йому: “ти зчитуватимеш дані із системного вхідного потоку (з клавіатури)”. Але це не єдина його функція! InputStreamReader
як отримує дані з потоку. Він ще й перетворює байтові потоки на символьні . Іншими словами, тобі вже не потрібно самому дбати про переклад лічених даних з “комп'ютерної” мови на “людську” – InputStreamReader
зробить усе за тебе. InputStreamReader
, звичайно, може читати дані не лише з консолі, а й з інших місць. Наприклад, із файлу:
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"));
}
}
Тут ми створабо вхідний потік даних FileInputStream
(це один з різновидів InputStream
), передали в нього шлях до файлу, а сам потік передали InputStreamReader
. Тепер він зможе читати дані з цього файлу, якщо файл цим шляхом існує, звичайно. Для читання даних (неважливо звідки, з консолі, файлу або ще звідкись) у класі InputStreamReader
теж використовується метод read()
. У чому різниця між System.in.read()
і InputStreamReader.read()
? Давай спробуємо рахувати ту саму букву "Я" за допомогою InputStreamReader
. Нагадаю, ось що вважав System.in.read()
:
Я
208
175
10
А як ту саму роботу проробить InputStreamReader
?
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);
}
}
}
Виведення в консоль:
Я
1071
10
Різницю видно відразу. Останній байт - для перенесення рядка - залишився без змін (число 10), а ось лічену букву "Я" було перетворено на єдиний код "1071". Це і є зчитування за символами! Якщо раптом не віриш, що код 1071 позначає букву "Я" - в цьому легко переконатися:)
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
char x = 1071;
System.out.println(x);
}
}
Виведення в консоль:
Я
Але якщо InputStreamReader
такий гарний, навіщо потрібен ще BufferedReader
? InputStreamReader
вміє і зчитувати дані, і конвертувати байти в символи — а що нам ще треба? Для чого ще один Reader? :/ Відповідь дуже проста - для більшої продуктивності і більшої зручності . Почнемо з продуктивності. BufferedReaderпри зчитуванні даних використовує спеціальну область - буфер, куди "складає" прочитані символи. У результаті, коли ці символи знадобляться нам у програмі, вони будуть взяті з буфера, а не безпосередньо з джерела даних (клавіатури, файлу тощо), а це заощаджує дуже багато ресурсів. Щоб зрозуміти як це працює — уяви, наприклад, роботу кур'єра у великій компанії. Кур'єр сидить в офісі та чекає, коли йому принесуть посилки на доставку. Щоразу, отримавши нову посилку, він може відразу вирушати в дорогу. Але посилок протягом дня може бути багато, і йому доведеться щоразу мотатися між офісом та адресаами. Натомість кур'єр поставив в офісі коробку, куди всі бажаючі складають свої посилки. Тепер кур'єр може спокійно взяти коробку і вирушати за адресаами - він заощадить дуже багато часу. адже йому не доведеться щоразу повертатися до офісу. Коробка в цьому прикладі є буфером, а офіс — джерелом даних. Кур'єру набагато простіше при доставці брати лист із загальної коробки, ніж щоразу їхати до офісу. Ще й бензин заощадить. Так само і в програмі — набагато менш витратно за ресурсами брати дані з буфера, а не звертатися щоразу до джерела даних. ТомуBufferedReader
+ InputStreamReader
працює швидше, ніж просто InputStreamReader
. З продуктивністю розібралися, а що із зручністю? Головний плюс у тому, що BufferedReader
вміє читати дані не тільки по одному символу (хоча метод read()
для цих цілей у нього теж є), а ще цілими рядками! Робиться це за допомогою методу readLine()
;
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("Ми рахували з клавіатури цей рядок:");
System.out.println(s);
}
}
Виведення в консоль:
JavaRush - найкращий сайт для вивчення Java!
Мы считали с клавиатуры эту строку:
JavaRush — лучший сайт для изучения Java!
Це особливо зручно у разі читання великого обсягу даних. Одну-два рядки тексту можна вважати посимвольно. А ось вважати "Війну і мир" за однією літерою буде вже дещо проблематично :) Тепер робота потоків стала набагато зрозумілішою для тебе. Для подальшого вивчення – ось тобі додаткове джерело:
Тут ти можеш прочитати докладніше про вхідні та вихідні потоки. Відеоогляд BufferedReader
'a одного з наших учнів. Так-так, наші учні не лише навчаються самі, а й записують навчальні відео для інших! Не забудь поставити лайк і передплатити наш канал :)
- BufferedReader/InputStreamReader - одна з лекцій JavaRush, присвячена
BufferedReader
іInputStreamReader
- Class InputStreamReader
- Class BufferedReader - документація Oracle про класи
BufferedReader
таInputStreamReader
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ