JavaRush /Java блог /Java Developer /Практика работы с классами BufferedReader и InputStreamRe...
Автор
Jesse Haniel
Главный архитектор программного обеспечения в Tribunal de Justiça da Paraíba

Практика работы с классами BufferedReader и InputStreamReader

Статья из группы Java Developer
Привет! Сегодняшняя лекция будет разделена на две условные части. Мы повторим кое-что из старых тем, которых уже касались ранее, и рассмотрим некоторые новые фичи :) Практика работы с классами BuffreredReader и InputStreamReader - 1Начнем с первого. Повторение — мать учения :) Ты уже не раз пользовался таким классом как BufferedReader. Эту команду, надеюсь, ты еще не успел забыть:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Прежде чем читать дальше, попробуй вспомнить за что отвечает каждая составляющая (System.in, InputStreamReader, BufferedReader) и для чего они нужны. Получилось? Если нет — не страшно :) Если к этому моменту ты о чем-то позабыл, перечитай еще раз эту лекцию, посвященную ридерам. Вкратце вспомним, что умеет каждый из них. System.in — это поток для получения данных с клавиатуры. В принципе, чтобы реализовать логику чтения текста, нам хватило бы и его одного. Но, как ты помнишь, System.in умеет считывать только байты, а не символы:

public class Main {

   public static void main(String[] args) throws IOException {

       while (true) {
           int x = System.in.read();
           System.out.println(x);
       }
   }
}
Если мы выполним этот код и введем в консоли букву «Й», вывод будет таким:

Й
208
153
10
Символы кириллицы занимают в памяти 2 байта, которые и выводятся на экран (а число 10 — это байтовое представление переноса строки, т.е. нажатия Enter). Чтение байтов — такое себе удовольствие, поэтому использовать System.in в чистом виде будет неудобно. Для того, чтобы считывать понятные всем кириллические (и не только) буквы, мы используем 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);
       }
   }
}
Если мы введем в консоль ту же букву «Й», результат в этот раз будет иным:

Й
1049
10
InputStreamReader преобразовал два считанных байта (208, 153) к единому числу 1049. Это и есть считывание по символам. 1049 соответствует букве «Й», в чем можно легко убедиться:

public class Main {

   public static void main(String[] args) throws IOException {

       char x = 1049;
       System.out.println(x);
   }
}
Вывод в консоль:

Й
Ну а что касается BufferedReader’a (да и вообще — BufferedЧегоУгодно), буферизированные классы используются для оптимизации производительности. Обращение к источнику данных (файлу, консоли, ресурсу в Сети) — достаточно дорогая в плане производительности операция. Поэтому чтобы сократить число таких обращений, BufferedReader считывает и накапливает данные в специальном буфере, откуда потом мы можем их получить. В результате количество обращений к источнику данных сокращается в разы или даже десятки раз! Еще одна дополнительная фича BufferedReader’a и его преимущество над обычным InputStreamReader’ом — это крайне полезный метод 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);
       reader.close();
   }
}

BufferedReader+InputStreamReader работает быстрее, чем просто InputStreamReader
Пользователь ввел следующий текст:
BufferedReader+InputStreamReader работает быстрее, чем просто InputStreamReader
Практика работы с классами BuffreredReader и InputStreamReader - 2Разумеется, BufferedReader — очень гибкий механизм, и позволяет работать не только с клавиатурой. Считывать данные можно, например, напрямую из Сети, просто передав ридеру нужный URL:

public class URLReader {
   public static void main(String[] args) throws Exception {

       URL oracle = new URL("https://www.oracle.com/index.html");
       BufferedReader in = new BufferedReader(
               new InputStreamReader(oracle.openStream()));

       String inputLine;
       while ((inputLine = in.readLine()) != null)
           System.out.println(inputLine);
       in.close();
   }
}
Можно считывать данные из файла, передав путь к нему:

public class Main {
   public static void main(String[] args) throws Exception {

       FileInputStream fileInputStream = new FileInputStream("testFile.txt");
       BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream));

       String str;

       while ((str = reader.readLine()) != null)   {
           System.out.println (str);
       }

       reader.close();
   }
}

Подмена System.out

Теперь давай рассмотрим одну интересную возможность, которую мы ранее не затрагивали. Как ты наверняка помнишь, в классе System есть два статических поля — System.in и System.out. Эти братья-близнецы являются объектами классов-потоков. System.in — абстрактного класса InputStream. А System.out — класса PrintStream. Сейчас мы поговорим именно о System.out. Если мы зайдем в исходный код класса System, увидим вот что:

public final class System {

……………...

public final static PrintStream out = null;

  …………

}
Итак, System.out — просто обычная статическая переменная класса System. Никакой магии в ней нет :) Переменная out относится к классу PrintStream. Вот интересный вопрос: а почему при выполнении кода System.out.println() вывод производится именно в консоль, а не куда-то еще? И можно ли это как-то изменить? Например, мы хотим считывать данные из консоли, и записывать их в текстовый файл. Можно ли как-то реализовать такую логику, не используя дополнительные классы ридеров и райтеров, а просто пользуясь System.out? Еще как можно :) И хотя переменная System.out обозначена модификатором final, мы все равно можем это сделать! Практика работы с классами BuffreredReader и InputStreamReader - 3Итак, что нам для этого нужно? Во-первых, нужен новый объект класса PrintStream вместо нынешнего. Текущий объект, установленный в классе System по умолчанию, нам не подходит: он указывает на консоль. Надо создать новый, который будет указывать на текстовый файл в качестве «места назначения» для наших данных. Во-вторых, нужно понять, как присвоить новое значение переменной System.out. Просто так этого не сделать, ведь она помечена final. Начнем с конца. В классе System как раз есть нужный нам метод — setOut(). Он принимает на вход объект PrintStream и устанавливает его в качестве точки вывода. Как раз то, что нам нужно! Осталось только создать объект PrintStream. Это сделать тоже несложно:

PrintStream filePrintStream = new PrintStream(new File("C:\\Users\\Username\\Desktop\\test.txt"));
Код целиком будет выглядеть так:

public class SystemRedirectService {

   public static void main(String arr[]) throws FileNotFoundException
   {
       PrintStream filePrintStream = new PrintStream(new File("C:\\Users\\Username\\Desktop\\test.txt"));

       /*Сохраним текущее значение System.out в отдельную переменную, чтобы потом
       можно было переключиться обратно на вывод в консоль*/
       PrintStream console = System.out;

       // Присваиваем System.out новое значение
       System.setOut(filePrintStream);
       System.out.println("Эта строка будет записана в текстовый файл");

       // Возвращаем System.out старое значение
       System.setOut(console);
       System.out.println("А эта строка - в консоль!");
   }
}
В результате первая строка будет записана в текстовый файл, а вторая — выведена в консоль :) Ты можешь скопировать этот код в свою IDE и запустить. Открыв текстовый файл, ты увидишь, что нужная строка успешно туда записалась :) На этом лекция подходит к концу. Сегодня мы вспомнили, как работать с потоками и ридерами, восстановили в памяти, чем они отличаются друг от друга и узнали о новых возможностях System.out, которой пользовались чуть ли не в каждом уроке :) До встречи на следующих лекциях!
Комментарии (74)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Rustam Уровень 35 Student
19 августа 2023
Наконец понял подмену System.out Спасибо!
Ислам Уровень 33
7 июля 2023
Nice
Maria Уровень 41
6 июля 2023
Спасибо вам огромное за статью. В обычной лекции про подмену вообще ничего непонятно. напишу туда, чтобы вас сразу читали, а не мучались там по 10 раз напрасно перечитывая
ivan Уровень 40
16 мая 2023
BufferedReader а не BuffreredReader. хоть кто-нибудь заметил ошибку в названии статьи?
Den Уровень 37
18 февраля 2023
InputStreamReader преобразовал два считанных байта (208, 153) к единому числу 1049 кто-нибудь может подсказать как получить этот результат? у меня какая-то жесть выходит, когда сдвигаю первую часть на 1 байт.

parts[0]=208, parts[1]=153
parts[0]<<8=53248
parts[1]<<0=153
(parts[0]<<8) | (parts[1]<<0) = 53401 or 킙
Юрий Кохно Уровень 62 Expert
27 января 2023
шел 2023 - ошибку в заголовке так и не исправили
Сергей В. Уровень 32
3 ноября 2022
"Вот интересный вопрос: а почему при выполнении кода System.out.println() вывод производится именно в консоль, а не куда-то еще?" И почему?
13 сентября 2022
помню гдето тут когдато писали что хороший программер это чей код читабелен и понятен. вот нахрена нужно мучить этот System.out если пользоваться им не явно для вывода в консоль а какуюто чушь придумывать чтоб кудато вместо этого записывало?!?!?! если надо чтоб кудато записало то так и накодировать чтобы всем было понятно
Lyokha Blagodatskikh Уровень 48
24 июля 2022
2022 - ошибку в заголовке так и не исправили )
Иван Голубев Уровень 108 Expert
21 февраля 2022
по знавательно