Практика работы с классами BuffreredReader и 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, которой пользовались чуть ли не в каждом уроке :) До встречи на следующих лекциях!
Комментарии (61)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Lyokha Blagodatskikh Уровень 29, Ural, Russian Federation
24 июля 2022
2022 - ошибку в заголовке так и не исправили )
Иван Голубев Уровень 64, Москва, Россия
21 февраля 2022
по знавательно
SomeBoy Уровень 35, Москва, Russian Federation
8 января 2022
Сейчас конец 9лвла курса Core. Не помню, чтобы речь заходила о классе File.
Михаил Уровень 22, Санкт-Петербург
16 сентября 2021
Из серии "А ты, дурак, решил по-старинке, а можно было вот как". Если что, рефлексия на последнюю задачу раздела 9.8
Руслан Уровень 30, Стерлитамак, Россия
5 августа 2021
Круто. Представьте себе. Я пишу текс на сайте JavaRush, а это текст идёт не в консоль, а на сайт, походу с помощью данного кода.
hidden #2595317 Уровень 45
3 августа 2021
В заголовке ошибку поправьте: Buff(r)eredReader
Илья Уровень 31, Москва, Россия
11 июля 2021
Подскажите пожалуйста - если System.in и System.out являются static final, то как тогда работают методы System.setIn() и System.setOut()? Это реализовано с помощью рефлексии?
Ivan Уровень 22, Санкт-Петербург
7 марта 2021
То задачи по еще не пройденному материалу будущих уровней из квеста Multithreading, то разжевывание одного и того же по 10 раз...
Future Man Уровень 25
7 января 2021
Все бы эти лекции да только в начале...
Deniska Уровень 10, Москва, Россия
6 января 2021
хм, видимо я очень сильно туплю. Мы считываем символы - и как показано выше это байты (цифры которые мы записываем в int), я не понимаю какой магией, они следующей строчкой оказываются в String уже строкой с буквами, а не цифрами..? надеюсь я понятно написал суть своего непонимания.