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, которой пользовались чуть ли не в каждом уроке :) До встречи на следующих лекциях!
Комментарии (75)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Zhandos
Уровень 33
23 марта, 15:47
как метод инпутстримридера преобразовал два байта в единое число какая логика там побитовые операции?
Rustam
Уровень 35
Student
19 августа 2023, 20:21
Наконец понял подмену System.out Спасибо!
Dmitry Vidonov
Уровень 29
Expert
25 октября 2023, 13:08
Понял эту фигню через многократное пропечатывание кода замены в задачках с этого уровня. Плюс каменты писал. И по итогу весь процесс уложился в понимании.
Ислам
Уровень 33
7 июля 2023, 10:22
Nice
Maria
Уровень 41
6 июля 2023, 09:29
Спасибо вам огромное за статью. В обычной лекции про подмену вообще ничего непонятно. напишу туда, чтобы вас сразу читали, а не мучались там по 10 раз напрасно перечитывая
ivan дворник
16 мая 2023, 15:53
BufferedReader а не BuffreredReader. хоть кто-нибудь заметил ошибку в названии статьи?
Den DevOps
18 февраля 2023, 02:18
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 킙
Den DevOps
18 февраля 2023, 02:21
тут весь код
public static void read2bytes () throws IOException {
    byte[] input = System.in.readNBytes(2);
    short[] parts = new short[input.length];
    for (int i = 0; i < input.length; i++) {
        if(input[i]<0){
            parts[i]= (short) (256+input[i]);
        }
        else {
            parts[i]=input[i];
        }
    }
    System.out.printf("parts[0]=%d, parts[1]=%d\n", parts[0], parts[1]);
    int move1 = 8;
    int move2 = 0;
    System.out.printf("parts[0]<<%d=%d\n", move1, parts[0]<<move1);
    System.out.printf("parts[1]<<%d=%d\n", move2, parts[1]<<move2);
    int result = ((parts[0])<<move1) | (parts[1]<<move2);
    System.out.printf("(parts[0]<<%d) | (parts[1]<<%d) = %d or %s\n", move1, move2, result, (char)result);
}
Gans Electro
Уровень 50
11 апреля 2023, 10:15
https://www.youtube.com/watch?v=9kxUO4Sv0VA
Юрий Кохно
Уровень 62
Expert
27 января 2023, 15:46
шел 2023 - ошибку в заголовке так и не исправили
ivan дворник
16 мая 2023, 15:53
++
Сергей В.
Уровень 32
3 ноября 2022, 23:28
"Вот интересный вопрос: а почему при выполнении кода System.out.println() вывод производится именно в консоль, а не куда-то еще?" И почему?
Алексей Щукин
Уровень 26
19 января 2023, 03:18
Не претендую на точность, но предполагаю, что разработчики Java просто так решили, что по умолчанию в статической переменной System.out хранится поток вывода в консоль.
13 сентября 2022, 18:25
помню гдето тут когдато писали что хороший программер это чей код читабелен и понятен. вот нахрена нужно мучить этот System.out если пользоваться им не явно для вывода в консоль а какуюто чушь придумывать чтоб кудато вместо этого записывало?!?!?! если надо чтоб кудато записало то так и накодировать чтобы всем было понятно
Lyokha Blagodatskikh
Уровень 48
24 июля 2022, 11:35
2022 - ошибку в заголовке так и не исправили )
Михаил
Уровень 20
4 августа 2022, 13:14
это байт на коменты=D