JavaRush /Курсы /Java Collections /BufferedReader, BufferedWriter

BufferedReader, BufferedWriter

Java Collections
2 уровень , 5 лекция
Открыта

— И это снова я.

— Привет, Элли!

— Сегодня я хочу тебе подробно рассказать про BufferedReader и BufferedWriter.

— Так ты мне уже рассказывала все про них. Ну ничего там сложного нет.

— Ок. Расскажи, как работает BufferedReader.

— BufferedReader — это как переходник в розетке с 110 к 220 вольт.

В конструктор объекта BufferedReader обязательно нужно передать объект Reader, из которого он будет читать данные. Объект BufferedReader читает из Reader’а данные большими кусками и хранит их у себя внутри в буфере. Поэтому чтение из пары BufferedReader+Reader быстрее, чем прямо из Reader.

— Верно. А BufferedWriter?

— Тут тоже все просто. Когда мы пишем в FileWriter, например, то данные сразу записываются на диск. Если мы часто пишем небольшие данные, то происходит много обращений к диску, что замедляет работу программы. А если мы используем BufferedWriter в качестве «переходника», то операция записи на диск ускорится. BufferedWriter, при записи в него, сохраняет переданные данные во внутреннем буфере, а когда буфер заполняется – пишет данные во Writer одним большим куском. Это гораздо быстрее.

— Гм. Все верно. А что ты забыл?

— После окончания записи у объекта BufferedWriter надо вызвать метод flush(), чтобы он записал данные из буфера во Writer, которые еще не записаны, т.е. буфер не заполнен до конца.

— А кроме того?

— А кроме того, пока буфер еще не записан во Writer, данные можно удалить и/или заменить на другие.

— Амиго! Я поражена! Да ты просто эксперт. Ладно, тогда я расскажу тебе о новых классах: ByteArrayStreamPrintStream.

Итак, ByteArrayInputStream и ByteArrayOutputStream.

Эти классы по сути чем-то похожи на StringReader и StringWriter. Только StringReader читал символы (char) из строки (String), а ByteArrayInputStream читает байты из массива байт (ByteArray).

StringWriter писал символы (char) в строку, а ByteArrayOutputStream пишет байты в массив байт у него внутри. При записи в StringWriter строка внутри него удлинялась, а при записи в ByteArrayOutputStream его внутренний массив байт тоже динамически расширяется.

Вот пример, который выводит в консоль полученную строку:

Чтение из объекта reader и запись в объект writer:
public static void main (String[] args) throws Exception
{
 String test = "Hi!\n My name is Richard\n I'm a photographer\n";
 StringReader reader = new StringReader(test);

 StringWriter writer = new StringWriter();

 executor(reader, writer);

 String result = writer.toString();

 System.out.println("Результат: "+result);
}

public static void executor(Reader reader, Writer writer) throws Exception
{
 BufferedReader br = new BufferedReader(reader); String line; while ((line = br.readLine()) != null) { writer.write(line + '\n'); } }

Вот как он будет выглядеть, если тут работать не с символами, а с байтами:

Чтение из объекта InputStream и запись в объект OutputStream:
public static void main (String[] args) throws Exception
{
 String test = "Hi!\n My name is Richard\n I'm a photographer\n";
 InputStream inputStream = new ByteArrayInputStream(test.getBytes());

 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

 executor(inputStream, outputStream);

 String result = new String(outputStream.toByteArray());
 System.out.println("Результат: "+result);
}

public static void executor(InputStream inputStream, OutputStream outputStream) throws Exception
{
 BufferedInputStream bis = new BufferedInputStream(inputStream);
 while (bis.available() > 0)
 {
  int data = bis.read();
  outputStream.write(data);
 }
}

Тут все аналогично примеру выше. Вместо String – ByteArray. Вместо Reader – InputStream, вместо Writer – OutputStream.

Единственные еще два момента – это преобразование строки в массив байт и обратно. Как ты видишь, это делается довольно несложно:

Преобразование строки в массив байт и обратно
public static void main (String[] args) throws Exception
{
 String test = "Hi!\n My name is Richard\n I'm a photographer\n";
 byte[] array = test.getBytes();

 String result = new String(array);
 System.out.println("Результат: "+result);
}

Чтобы получить байты, которые уже добавлены в ByteArrayOutputStream, надо вызвать метод toByteArray().

— Ага. Аналогия с StringReader/StringWriter довольно сильная, особенно когда ты мне ее показала. Спасибо, Элли, действительно интересный урок.

— Куда это ты спешишь? У меня есть еще небольшой подарок – хочу рассказать тебе про класс PrintStream.

— PrintStream? В первый раз слышу о таком классе.

— Ага. Особенно, если не считать, что ты им пользуешься с первого дня, когда ты начал изучать Java. Помнишь System.out? так вот – System.out – это статическая переменная класса System типа… PrintStream! Именно оттуда растут ноги всех этих print, println и т.д.

— Ого. Как интересно. Я как-то ни разу и не задумывался. Расскажи подробнее.

— Гуд. Тогда слушай. Класс PrintStream был придуман для читабельного вывода информации. Он практически весь состоит из методов print и println. См. таблицу:

Методы Методы
void print(boolean b) void println(boolean b)
void print(char c) void println(char c)
void print(int c) void println(int c)
void print(long c) void println(long c)
void print(float c) void println(float c)
void print(double c) void println(double c)
void print(char[] c) void println(char[] c)
void print(String c) void println(String c)
void print(Object obj) void println(Object obj)
  void println()
PrintStream format (String format, Object ... args)
PrintStream format (Locale l, String format, Object ... args)

Также есть несколько методов format, чтобы можно было выводить данные на основе шаблона. Пример:

Преобразование строки в массив байт и обратно
String name = "Kolan";
int age = 25;
System.out.format("My name is %s. My age is %d.", name, age);
Вывод на экран:
My name is Kolan. My age is 25.

— Ага, помню, мы уже когда-то разбирали метод format у класса String.

— На этом все.

— Спасибо, Элли.

Комментарии (85)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Denis Odesskiy Уровень 47
31 августа 2024
Первая программа, System.out.println("Hello World!"), вот когда мы только узнали как она работает. Сложна программа выходит😄, можно на собеседовании спрашивать. Типа, а ну-ка напишите программу которая выводит на консоль "Hello World!". А теперь, коллеги, давайте копнем глубже! Мы все знаем, что System.out.println("Hello World!") — это основа основ, но задумайтесь: как это чудо кодирования работает под капотом? Вы, конечно, знаете, что System.out — это не просто какая-то переменная, а экземпляр класса PrintStream, верно? Ладно, допустим. Но как же этот экземпляр создается и как JVM решает, что именно он должен быть связан с консольным выводом? Что произойдет, если мы заменим System.out на другой поток вывода? Например, давайте отправим наш "Hello World!" в файл или, не побоюсь этого слова, в удалённый сервер! Но какой объект вообще отвечает за настройку этого "потока потоков"? Не забывайте, что именно здесь появляется магия метода setOut(PrintStream out)! Но это еще не всё! Как JVM решает, когда вызвать метод println? Что вообще означает вызов метода на уровне байт-кода, и как выполняется процесс интерпретации этого вызова? Какой системный вызов делает JVM, чтобы "прокинуть" данные на консоль? И наконец, главный вопрос: что будет, если случайно поменять местами байты в этом всемогущем "Hello World"? Вспоминайте реализацию String в Java, а также механизмы кодировки и декодировки строк в UTF-8. А вдруг это не просто строка, а UTF-16, спрятанный в бинарный файл? Как JVM справляется с такими каверзными задачками? В общем, кажется, без глубокого анализа байт-кода и запуска дебаггера нам тут не обойтись. А для самых продвинутых — не забудьте запустить вашу программу на многопоточном сервере и следить за тем, как println справляется с потокобезопасностью! 😄
SomeBody098 Уровень 51
19 сентября 2024
да, я еще помню задачи на JR про setOut, очень интересно теперь осознавать как это работает под капотом, спасибо за коммент )))
Евгений N Уровень 23
4 апреля 2024
так 50 лет назад на Бэйсике писали. где стримы ?
22 февраля 2024
Уф достало
ElenaN Уровень 37
17 декабря 2023
Так-с, а коллекции-то где? или я не на ту кнопку нажала?)
Egor Уровень 40
21 января 2024
это как с 3 модулем про многопоточность. Сама многопоточность на 5 уровне начинается и заканчивается на 8.
ivan Уровень 40
30 июня 2023
вовремя
Kitsune Уровень 34
15 июля 2023
я бы сказал "неожиданно"
Lipovskyi Volodymyr Уровень 36
18 августа 2022
Мені здається, що програмування - це тільки Input та Output. Кожен урок одне й теж саме. Дуже сподіваюсь, що це не так🧐 До речі ByteArrayStream та PrintStream я вже використовував не один раз. Не пам'ятаю де саме, але це точно.
runk out Уровень 51
1 декабря 2022
Приблизительно так все и выходит - получить откуда-то информацию, обработать, куда-то вернуть.
Rolik Уровень 41
9 мая 2023
Да, в основном все парсят и затем форматируют обратно. Т.е., все работают с байтами. Со строками мало кто.
Buenos Уровень 49
25 июля 2022
Читаю как детектив - никогда не знаешь чем закончится... Тут тебе БафердРидер/Райтер, и вдруг ни с того ни с сего "...а ну ладно, поговорим о других потоках"... вот это поворот...)))
Роман Кончалов Уровень 28 Expert
24 января 2022
пока буфер еще не записан во Writer, данные можно удалить и/или заменить на другие Это как? Записать данные через другой объект, а потом закрыть ресурс?
LuneFox Уровень 41 Expert
22 декабря 2021
Вот этот момент интересует:

 while (bis.available() > 0)
 {
  int data = bis.read();
  outputStream.write(data);
 }
Что хранится в data? Если одно интовое значение, то в чём смысл буффера, если мы перекачиваем данные по одному инту? Второй момент: "При записи в ByteArrayOutputStream его внутренний массив байт тоже динамически расширяется." Внутренний массив, получается, представлен не в виде byte[]? Потому что, насколько я помню, примитивные массивы не могут менять размер после создания. Или каждый раз, когда нужно расширить массив, создаётся новый и в него переписывается всё из старого? А как же тогда производительность?
Stepan Уровень 27
24 декабря 2021
По моему мнению: 1. BufferedInputStream.read() отдает данные из буфера, не нагружая или не дожидаясь источник потока данных, т.е. он заполняет свой буфер до выполнения BufferedInputStream.read(). При чтении из буфера, высвобожденное место занимается новыми данными из источника данных. 2. Внутренний массив ByteArrayOutputStream увеличивается при необходимости, ЕМНИП, кратно 2, а изначально 8192 байт. Да, создается новый и туда копируются данные. Производительность, как минимум, зависит от скорости данных к потребителю (запись в файл, передача по сети, иная обработка данных), размера этих данных, начального размера буфера.
LuneFox Уровень 41 Expert
25 декабря 2021
То есть, BIS читает сразу полный буффер, а отдаёт нам по 1 байтику? И когда истощается, зачёрпывает ещё полный буффер?
Stepan Уровень 27
26 декабря 2021
Вот тут достоверно не могу утверждать, но пока что я считаю, что BIS не ожидает опустошения буфера, а заполняет его сразу же, как появилось свободное место, т.е. это очередь, которая всегда должна быть заполнена.
fedyaka Уровень 36
2 февраля 2022
Да, он берёт информацию крупными кусками, а после по запросу отдаёт её сколько нужно из буфера.
Ars Уровень 41
17 ноября 2021
Где-то в середине статьи (а точнее в первом примере в методе public static void executor(Reader reader, Writer writer) throws Exception) забыли закрыть тег жирного шрифта.