JavaRush/Java блог/Java Developer/Считывание с клавиатуры — «ридеры»
Автор
Aditi Nawghare
Инженер-программист в Siemens

Считывание с клавиатуры — «ридеры»

Статья из группы Java Developer
участников
Привет! В лекциях и задачах мы научились выводить данные в консоль, и наоборот — считывать данные с клавиатуры. Считывание с клавиатуры — «ридеры» - 1Ты даже научился использовать для этого сложную конструкцию:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Но на один вопрос мы так и не ответили.

А как это вообще работает?

На самом деле, любая программа чаще всего существует не сама по себе. Она может общаться с другими программами, системами, интернетом и т.д. Под словом “общаться” мы в первую очередь подразуемеваем “обмениваться данными”. То есть, принимать какие-то данные извне, а собственные данные — наоборот, куда-то отправлять. Примеров обмена данными между программами много даже в повседневной жизни. Так, на многих сайтах ты можешь вместо регистрации авторизоваться при помощи своего аккаунта в Facebook или Twitter. В этой ситуации две программы, скажем, Twitter и сайт, на котором ты пытаешься зарегистрироваться, обмениваются необходимыми данными между собой, после чего ты видишь конечный результат — успешную авторизацию. Для описания процесса обмена данными в программировании часто используется термин “поток”. Откуда вообще взялось такое название? “Поток” больше ассоциируется с рекой или ручьем, чем с программированием. На самом деле, это неспроста :) Поток — это, по сути, перемещающийся кусок данных. То есть в программировании по потоку “течет” не вода, а данные в виде байтов и символов. Из потока данных мы можем получать данные частями и что-то с ними делать. Опять же, применим "водно-текучую" аналогию: из реки можно зачерпнуть воды, чтобы сварить суп, потушить пожар или полить цветы. При помощи потоков ты можешь работать с любыми источниками данных: интернет, файловая система твоего компьютера или что-то еще — без разницы. Потоки — инструмент универсальный. Они позволяют программе получать данные отовсюду (входящие потоки) и отправлять их куда угодно (исходящие). Их задача одна — брать данные в одном месте и отправлять в другое. Потоки делятся на два вида:
  1. Входящий поток (Input) — используется для приема данных
  2. Исходящий поток (Output) — для отправки данных.
Входящий поток данных в Java реализован в классе 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);
       }
   }
}
В классе InputStreamSystem.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 от одного из наших учеников. Да-да, наши ученики не только учатся сами, но и записывают обучающие видео для других! Не забудь поставить лайк и подписаться на наш канал :)
Лучше с самого начала учебы приучать себя к чтению официальной документации. Она является главным источником знаний по языку, и большинство ответов всегда можно найти там.
Комментарии (323)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
muromtsev
Уровень 20
22 апреля, 16:33
У Вас авторы статей не из России?
qaseqiara
Уровень 11
15 апреля, 07:19
кто тоже задался вопросом в чем отличие от сканера, то вот что я нашел в интернете: Scanner: Позволяет выполнять настройку разделителей, упрощает адаптацию под различные типы ввода. BufferedReader: Ограничен символами новой строки, но при этом исключительно эффективен при непрерывном чтении данных.
dmtr_mkhlv Backend Developer
17 апреля, 19:28
1. `BufferedReader` - это класс в Java, который обеспечивает буферизированное чтение символов из символьного ввода. Он позволяет эффективно считывать символы, массивы символов и строки. 2. `InputStreamReader` - это мост между байтовыми потоками и символьными потоками. Он читает байты и декодирует их в символы, используя указанную кодировку. В данном случае, он используется для чтения символов из стандартного потока ввода (`System.in`). 3. `System.in` - это объект типа `InputStream`, который представляет стандартный ввод, обычно связанный с консолью или клавиатурой. Итак, `BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));` создает объект `BufferedReader` с именем `reader`, который будет читать ввод из консоли, используя `InputStreamReader` для перевода байтов в символы. Это позволяет нам считывать ввод пользователя в Java при запуске программы из командной строки.
dmtr_mkhlv Backend Developer
17 апреля, 19:28
`BufferedReader` и `Scanner` - это два разных класса в Java, которые могут использоваться для чтения пользовательского ввода, но они имеют разные особенности и подходы к этой задаче. Вот основные различия: 1. **Гибкость:** - `BufferedReader` предоставляет базовые операции чтения строк и символов. Он не имеет методов для парсинга различных типов данных, таких как `int`, `double` и т. д. - `Scanner`, с другой стороны, предоставляет удобные методы для чтения различных типов данных, таких как `nextInt()`, `nextDouble()` и т. д. Он также может использоваться для чтения строк и символов. 2. **Производительность:** - `BufferedReader` более эффективен при чтении больших объемов данных, поскольку он буферизирует ввод. - `Scanner` может быть медленнее, особенно при чтении больших файлов или ввода, потому что он выполняет более сложные операции, такие как парсинг. 3. **Использование:** - `BufferedReader` часто используется для простого чтения строк или символов из потока ввода, такого как консольный ввод или чтение текстовых файлов. - `Scanner` обычно используется для более сложного анализа ввода, когда требуется чтение различных типов данных и разделение ввода на лексемы. Таким образом, если вам нужно просто прочитать строки или символы из ввода, `BufferedReader` может быть предпочтительным выбором из-за своей производительности. Однако, если вам нужно выполнять сложный анализ ввода, включая чтение различных типов данных, то `Scanner` может быть более удобным.
Дмитрий
Уровень 4
5 марта, 09:52
Кажется, это явно не для 2 лвл 😑
даня клава Работает в dota 2
9 февраля, 14:40
Хорошая статья, четко и внятно
YUREC
Уровень 36
8 февраля, 22:35
Эту статью надо читать на 16 уровне, а не на 2м.
даня клава Работает в dota 2
9 февраля, 14:40
некоторые уже имели некий опыт работы с джавой,поэтому при начале обучения выбрали немного иной уровень :)
tribalism
Уровень 4
10 февраля, 18:24
вот я сейчас открыл на втором уровне и тут внезапно "Вы научились использовать сложную конструкцию BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));" А я что-то не вспомню когда я это успел научиться)
YUREC
Уровень 36
10 февраля, 18:58
да, тут то что дополнительно зачастую никак не соответствует пройденному. А насчет BufferedReader и прочего из потоков я могу сказать готовьтесь, легко не будет.😀
Danil Dumarevsky
Уровень 7
2 февраля, 14:07
Не знаю мне статья очень понравилась главное понять что с чем взаимодействует. По началу вообще нечего не понял, но при должном усилии всё-таки прозрел. Те у кого остались вопросы после прочтения лекции советую посмотреть видео в конце.
Настя
Уровень 2
1 февраля, 18:04
Не нашла информации про IOException и про добавление к методу main - throws IOException Остались вопросы..
Roman
Уровень 6
Expert
20 января, 16:51
Зрозумів деякі деталі. Особливо приємно вражений від System.in.read();😅
Максим Li Java Developer
15 января, 04:55
Хорошая статья!
YUREC
Уровень 36
7 января, 09:06
ввод данных в паскaле read(); readline(); 🥳 ввод данных в С#: Console.Read(); Console.ReadLine();😀 ввод данных в С++ : std::cin >>🙄 ввод данных Java: Scanner console = new Scanner(System.in); [тип данных] name = console.next[тип данных](); или BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String name = reader.readLine();😳