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

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

Статья из группы Java Developer
участников
Привет! Разберемся, как в Java считывать данные с клавиатуры и что означает конструкция BufferedReader + InputStreamReader. Коротко: System.in это входящий поток байтов от клавиатуры, InputStreamReader превращает байты в символы, а BufferedReader добавляет буфер (работает быстрее) и умеет читать строку целиком методом readLine().

Кратко

  • System.in это входящий поток байтов, привязанный к клавиатуре.
  • Потоки бывают входящие (InputStream) и исходящие (OutputStream), а также байтовые и символьные.
  • System.in.read() читает байты, поэтому для русских букв (2 байта) он неудобен.
  • InputStreamReader превращает байты в символы.
  • BufferedReader добавляет буфер (быстрее) и умеет читать строку целиком методом readLine().
  • Итоговая конструкция: new BufferedReader(new InputStreamReader(System.in)).
В лекциях и задачах мы научились выводить данные в консоль, и наоборот, считывать данные с клавиатуры. Считывание с клавиатуры в Java через связку BufferedReader и InputStreamReaderТы даже научился использовать для этого сложную конструкцию:
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 и метод read()

Начнем с конца. 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!

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);
   }
}
Вывод в консоль:

Я

BufferedReader: буфер и чтение строк

Но если 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!
Это особенно удобно в случае чтения большого объема данных. Одну-две строчки текста еще можно считать посимвольно. А вот считать "Войну и мир" по одной букве будет уже несколько проблематично :) Всю связку из трех классов удобно свести в таблицу:
КлассЧто делаетКлючевой метод
System.inвходящий поток байтов от клавиатурыread() (байты)
InputStreamReaderпревращает байты в символыread() (символы)
BufferedReaderдобавляет буфер и умеет читать строки целикомreadLine()
Теперь работа потоков стала гораздо более понятной для тебя. Для дальнейшего изучения вот тебе дополнительный источник: Здесь ты можешь прочитать подробнее о входящих и исходящих потоках. Видеообзор BufferedReader’a от одного из наших учеников. Да-да, наши ученики не только учатся сами, но и записывают обучающие видео для других! Не забудь поставить лайк и подписаться на наш канал :)
BufferedReader/InputStreamReader, одна из лекций JavaRush, посвященная BufferedReader и InputStreamReader
  • Class InputStreamReader
  • Class BufferedReader, документация Oracle о классах BufferedReader и InputStreamReader.
  • Лучше с самого начала учебы приучать себя к чтению официальной документации. Она является главным источником знаний по языку, и большинство ответов всегда можно найти там.

    Вопросы и ответы

    Что такое поток (stream) в Java?

    Поток это последовательность данных (байтов или символов), которая перемещается из источника в приемник. Входящие потоки принимают данные, исходящие отправляют.

    Зачем нужен InputStreamReader?

    Он читает данные из входящего потока и превращает байты в символы. Без него System.in.read() возвращал бы отдельные байты, что неудобно для русских букв, ведь они занимают 2 байта.

    Зачем нужен BufferedReader, если есть InputStreamReader?

    BufferedReader добавляет буфер: данные читаются из памяти, а не каждый раз из источника, поэтому работает быстрее. Плюс он умеет читать строку целиком методом readLine().

    Что делает System.in?

    Это объект класса InputStream, входящий поток, привязанный к клавиатуре. Через него программа получает данные, которые вводит пользователь.

    Как считать строку с клавиатуры в Java?

    Создать BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)) и вызвать reader.readLine(). Метод вернет введенную строку целиком.

    Почему System.in.read() возвращает числа, а не буквы?

    Метод read() читает байты и возвращает их числовые коды. Русская буква занимает 2 байта, поэтому выводится несколькими числами. InputStreamReader преобразует байты в символьные коды.

    Читайте также

    Комментарии (356)
    • популярные
    • новые
    • старые
    Для того, чтобы оставить комментарий Вы должны авторизоваться
    Sergey Fedorov
    Уровень 16
    14 января, 20:35
    объясните для тупого. Но ведь System.in это же не объект, а поле, то есть переменная in в которой сохранена ссылка на какой то объект?
    GrandAlexandr
    Уровень 5
    5 января, 14:14
    так ведь в задачах постоянно всплывает, условия частенько требуют ввести что-то с клавиатуры
    hidden #3614201
    Уровень 15
    6 августа 2025, 09:23
    Слушайте, честно говоря, статья написана очень классно, понятно стало, в чем отличие между Scanner, BufferedReader, InputStreamReader, спасибо)
    Gris Hiver
    Уровень 1
    27 июля 2025, 22:01
    Спасибо за видео ! После статьи вопросов появилось больше, чем было «до». Но в видео все разложено по полочкам.
    Ann
    Уровень 6
    Expert
    25 июля 2025, 18:37
    Доходчиво объяснили в видео, цитирую 2 вывода с дополнениями: 1. ВОТ СОБСТВЕННО ДЛЯ ЭТОГО МЫ И ГОРОДИЛИ ВСЮ ЭТУ КОНСТРУКЦИЮ, ЧТОБЫ РАБОТАТЬ НЕ С БАЙТАМИ, А СО СТРОКАМИ + чтобы улучшить производительность 2. reader - тот же объект System.in, но ОБЕРНУТЫЙ ДВА РАЗА, опять же чтобы работать не с байтами, а со строками и улучшить производительность. То есть на будущее автор видео дает понять, что еще будут встречаться такие обертки-декораторы, мол привыкайте.
    Aura
    Уровень 23
    5 июня 2025, 03:04
    Елки-палки, а что тут такие низкие уровни делают? Забейте на эту статью пока, сохраните ее и откроете ее на 16м уровне, на вашем 2-3-4-5 уровне тут явно не нужно это знать, это пока для вас лишняя информация, которая отобьет у вас желание учиться дальше.
    Alyona Paker
    Уровень 3
    4 июня 2025, 04:51
    откуда взялись цифры (Я) 208 175 10
    Андрей
    Уровень 21
    13 июля 2025, 17:45
    208 175 — это сырые байты Я в UTF-8. 1071 — это Unicode-код символа Я, который получается при правильной декодировке UTF-8. 208 175 10 (байты) 1071 10 (символы) Я кодируется 2 байтами: 208 175 208 // Первый байт 'Я' в UTF-8 175 // Второй байт 'Я' в UTF-8 10 // \n (перенос строки) InputStreamReader с кодировкой UTF-8 корректно объединяет байты 208 175 в символ Я (Unicode 1071). 10 — это \n (перенос строки). погугли Таблица кодов кириллицы в Unicode, UTF-8 и Windows-1251
    15 мая 2025, 07:27
    Автор явно искажает смысл слова поток ввода/вывода, InputStream, System.in и т.д., сами по себе не являются потоками ввода/вывода, поток это образное выражение, что бы обобщенно сказать о входящих данных и исходящих, абстрагируясь ещë и от уточнения самого приëмника и источника, а статья вводит в заблуждение новичков художественной литературой. В попытке перевода технической инфы в художку нет ничего хорошего. Всë, что выводит ваша программа или принимает на вход, обобщенно просто называется потоком ввода/вывода, точка, а все эти классы, объекты и т.п., ничто иное как связующее звено между источником и приемником, для новичка этой инфы должно быть достаточно, что бы уловить суть. В остальном же статья отличная. В ассемблерных же командах это свелось бы просто к "данным", "данные" - над которыми выполняет операцию "команда". А в языке джава это всë мы видим на более высоком уровне абстракций, в виде классов, методов и т.д.. P.S. Хотя, с точки зрения того, что все эти классы и т.п. поступают на вход в компмлятор, в таком смысле можно назвать этот класс потоком ввода, однако, в таком ключе рассмотрения, тоже самое будет справедливо и ко всем остальным классам и т.п..
    Grygorii Krepchuk
    Уровень 1
    17 апреля 2025, 22:07
    Сильно много информации в ваших дополнительных статьях - перегружаете мозги сильно...если оно не нужно - так зачем тогда....давайте только то что нужно - по максимуму - а вы в дополнительных статьях грузите больше чем на основных лекциях!Спасибо
    Anonymous #3446622
    Уровень 4
    3 апреля 2025, 06:40
    почему у меня не включаются не какие видо
    Alex42rr
    Уровень 4
    10 апреля 2025, 03:58
    открываю ютуб через впн