JavaRush /Курсы /Java Syntax Pro /Потоки вывода

Потоки вывода

Java Syntax Pro
15 уровень , 3 лекция
Открыта

1. Класс OutputStream

С потоками ввода мы только что разобрались. Настало время поговорить о потоках вывода.

Класс OutputStream является классом-родителем для всех классов, которые поддерживают байтовый вывод. Это абстрактный класс, который сам ничего не делает: для этого у него есть классы-наследники на все случаи жизни.

Сложновато звучит. Если попроще, этот класс оперирует байтами, а не, например, символами или другими типами данных. А то, что он абстрактный, значит, что мы обычно используем не его, а один из его классов-наследников. Например, FileOutputStream и ему подобные.

Но вернемся к классу OutputStream. У этого класса есть методы, которые обязаны реализовывать все его классы-наследники. Вот основные из них:

Методы Описание
void write(int b)
Записывает один байт (не int) в поток.
void write(byte[] buffer)
Записывает массив байт в поток
void write(byte[] buffer, off, len)
Записывает часть массива байт в поток
void flush()
Записывает в поток все данные, которые хранятся в буфере
void close()
Закрывает поток

При создании объекта класса-наследника InputStream обычно указывается объект-источник, из которого InputStream читает данные. При создании объекта класса-наследника OutputStream также обычно указывается целевой объект или целевой поток, в который будут записываться данные.

Вкратце пройдемся по всем методам класса OutputStream:

Метод write(int b)

Этот метод записывает в поток вывода один байт (не int). Переданное значение приводится к типу байт, три первые байта отбрасываются.

Метод write(byte[] buffer)

Записывает в поток вывода переданный массив байтов. Все.

Метод write(byte[] buffer, int offset, int length)

Записывает в поток вывода часть переданного массива байтов. Переменная offset задает номер первого элемента массива, length — длина записываемого фрагмента.

Метод flush()

Метод flush() используется, чтобы принудительно записать в целевой поток данные, которые могут кэшироваться в текущем потоке. Актуально при использовании буферизации и/или нескольких объектах потоков, организованных в цепочку.

Метод close()

Закрывает поток и освобождает все системные ресурсы, связанные с этим потоком. Метод close() можно не вызывать, если вы используете try-with-resources.

Пример — копирование файла

Код Примечание
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStream для чтения из файла
OutputStream для записи в файл

Буфер, в который мы будем считывать данные
Пока данные есть в потоке

Считываем данные в буфер
Записываем данные из буфера во второй поток

2. Класс Writer

Класс Writer — это полный аналог класса OutputStream, и снова только с одним отличием: он работает с символами, char, вместо байт.

Это абстрактный класс: объекты класса Writer создать нельзя. Его основная цель — быть единым классом-родителем для сотен классов-наследников и задать для них общие методы работы с символьными потоками.

Методы класса Writer (и всех его классов-наследников):

Методы Описание
void write(int b)
Записывает один символ (не int) в поток.
void write(char[] buffer)
Записывает массив символов в поток
void write(char[] buffer, off, len)
Записывает часть массива символов в поток
void write(String str)
Записывает строку в поток
void write(String str, off, len)
Записывает часть строки в поток
void flush()
Записывает в поток все данные, которые хранятся в буфере
void close()
Закрывает поток

Методы очень похожи на методы класса OutputStream, только работают с символами вместо байт.

Краткое описание методов:

Метод write(int b)

Этот метод записывает в поток вывода один символ char (не int). Переданное значение приводится к типу char, два первых байта отбрасываются.

Метод write(char[] buffer)

Записывает в поток вывода переданный массив символов.

Метод write(char[] buffer, int offset, int length)

Записывает в поток вывода часть переданного массива символов. Переменная offset задает номер первого элемента массива, length — длина записываемого фрагмента.

Метод write(String str)

Записывает в поток вывода переданную строку.

Метод write(String str, int offset, int length)

Записывает в поток вывода часть переданной строки: строку преобразуют в массив символов. Переменная offset задает номер первого элемента массива, length — длина записываемого фрагмента.

Метод flush()

Метод flush() используется, чтобы принудительно записать в целевой поток данные, которые могут кэшироваться в текущем потоке. Актуально при использовании буферизации и/или нескольких объектах потоков, организованных в цепочку.

Метод close()

Закрывает поток и освобождает все системные ресурсы, связанные с этим потоком. Метод close() можно не вызывать, если вы используете try-with-resources.

Пример программы, которая копирует текстовый файл:

Код Примечание
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Reader для чтения из файла
Writer для записи в файл

Буфер, в который будем считывать данные
Пока данные есть в потоке

Читаем данные в буфер
Записываем данные из буфера во второй поток

Класс StringWriter

Есть еще один интересный класс-наследник от класса Writer — это StringWriter. В нем находится изменяемая строка — объект StringBuffer. И каждый раз, когда вы что-то «пишете» в объект StringWriter, текст просто добавляется во внутренний буфер.

Пример:

Код Примечание
StringWriter writer = new StringWriter();
writer.write("Hello");
writer.write(String.valueOf(123));

String result = writer.toString();
Создается целевой символьный поток StringWriter
Строка пишется в буфер внутри StringWriter
Строка пишется в буфер внутри StringWriter

Преобразовываем содержимое объекта к строке

В данном случае класс StringWriter — это, по сути, обертка над классом StringBuffer, однако класс StringWriter — это наследник класса-потока Writer, и он может использоваться в цепочках из объектов-потоков. Довольно полезное свойство на практике.



3. Класс PrintStream

Классы потокового вывода тоже можно организовывать в цепочки с использованием потоков-посредников, которые записывают данные в переданный им целевой поток. Общая картинка взаимодействия этих потоков выглядит так:

Класс PrintStream

Самый интересный и многофункциональный из всех промежуточных потоков вывода — PrintStream. У него несколько десятков методов и аж целых 12 конструкторов.

Класс PrintStream унаследован от класса FilterOutputStream, а тот унаследован от OutputStream. Поэтому класс PrintStream имеет все методы классов-родителей и плюс свои. Вот самые интересные из них:

Методы Описание
void print(obj)
Преобразует переданный объект в строку и выводит в целевой поток.
void println(obj)
Преобразует переданный объект в строку и выводит в целевой поток. Добавляет в конце символ переноса строки
void println()
Выводит в целевой поток символ переноса строки
PrintStream format(String format, args...)
Конструирует и выводит строку на основе строки шаблона и переданных аргументов, по аналогии с методом String.format()

А где же несколько десятков методов, спросите вы?

Все дело в том, что у него много вариантов метода print() и println() с разными аргументами. Их вполне можно свести к этой таблице.

Мы даже не будем разбирать эти методы, т.к. вы их и так уже хорошо знаете. Догадываетесь, к чему я клоню?

Помните команду System.out.println()? А ведь ее можно записать в две строки:

Код Вывод на экран
PrintStream stream = System.out;
stream.println("Hello!");
Hello!

Наша любимая команда System.out.println() — это вызов метода println() у статической переменной out класса System. А тип у этой переменной — PrintStream.

Уже много уровней вы почти в каждой задаче вызываете методы класса PrintStream и даже не догадываетесь об этом!

Практическое использование

Есть в Java один интересный класс — ByteArrayOutputStream, который представляет из себя динамически увеличивающийся массив байт, унаследованный от OutputStream.

Объект ByteArrayOutputStream и объект PrintStream можно выстроить в такую цепочку:

Код Описание
ByteArrayOutputStream baos = new ByteArrayOutputStream();

try(PrintStream stream = new PrintStream(baos))
{
   stream.println("Hello");
   stream.println(123);
}

String result = baos.toString();

System.out.println(result);
Создали в памяти буфер для записи

Обернули буфер в объект PrintStream

Записывает данные как в консоль



Преобразовываем массив в строку!

Вывод на экран:
Hello!
123

Комментарии (471)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Букашка Уровень 27
21 января 2026
До этого некоторые лекции были терпимы, хоть и ужасно написаны, но этот уровень и данная статья пробили пол... Я хоть и человек с неким опытом в изучении языков программирования, но даже мне было трудно! Администраторам стоит пересмотреть этот уровень и написать его нормально, а не кинуть просто кучу инфы и никак ее не структурировать.
Vadim Уровень 18
14 января 2026
Руководство по Java Герберт Шилдт параллельно еще читаю.
Dmitry Gisinov Уровень 19
30 октября 2025
Я вообще не понимаю ни-чер-та, перечитывал лекции конспектировал, с ИИ разбирал, но это не дало понимания как решать задачи( лекции вообще как будто не связаны с задачами. А советы типа "делай всё по пунктам" не работают, особенно с последней задачей. При чём смотришь ответ и +- с пояснениями ии понимаешь как решается, но как к этому прийти самостоятельно, хрен пойми
Kadony Уровень 16
23 октября 2025
Зачем вообще теория дана в этой лекции если с задачами вообще связи нет, задачи все только топорными методами решаются как прописали в отладчике
Dmitry Gisinov Уровень 19
30 октября 2025
подписываюсь под каждым словом
Anton Zorin Уровень 21
12 сентября 2025
В принципе норм. Сначала пугает, потому что привык ковыряться в массивах и переменных с названием не длиннее i и jopa. Но потом становится более менее понятно, главное пока не лезть в нутрянку этого всего, а то на этом учеба закончится.
Bilal Уровень 32
13 августа 2025
Я на грани. До этого уровня все понимал, что было не понятно с первого раза старался разобрать по молекулам и становилось ясно. В начале этого уровня тоже казалось понимал, но сейчас вообще поплыл, все смешалось - открываю задачу и не понимаю ни черта, что и зачем, как будто открыл Java впервые. Никакой структуры не выстраивается - слишком много кусочков без общей картины. Пробую углубиться, но в итоге закапываюсь. Я один такой? Может пройтись по верхам и вернуться позже? Кто на опыте, "коллеги"
Вячеслав Казак Уровень 34
15 августа 2025
Не знаю ни одного человека, который эту тему полностью понял с первого раза. Возвращаться придется) Нарешивание задач + чтение документации поможет. В документации сперва смотреть иерархию классов и их конструкторы.
Nervo Id Уровень 32
31 августа 2025
Мне помогает ДипСик в таких случаях. Если тема вообще не понятна или я вижу, что темы перемешаны я использую ИИ, что бы структурировать в одну тему (просто собираю по кусочкам). На крайний случай скипаю тему и через ИИ + туторы с разных каналов смотрю и собираю их инфу вместе в понятное и читаемое. У JavaRush две проблемы: местами темы разбросаны, когда это не нужно и сбивает с ориентации на одну тему и их "компилятор", который, как я заметил ещё на 3 уровне, не все правильные варианты написания кода принимает.
Сергей Тыщенко Уровень 29
29 сентября 2025
Думаю мешает императивный (линейный) подход/понимание программирования. Не думаю что само понятие байтового потока кого то удивило, это древнейший механизм работы с периферией, скорее озадачила объектная конвейеризация. На java вполне можно писать последовательные алгоритмы, как на однопоточном Си. Но когда требуется понимание программы не как последовательного алгоритма, а как параллельных наборов связанных состояний , что реализуется ООП, функциональным программированием, да и просто многопоточностью - простые базовые механизмы языка сначала вызывают ступор. Как говорится с этим надо просто пожить, переспать, и наконец просветлиться. Наверно беда в том, что мышление способных к программной разработке людей генетически заточено на однопоточность и приходится в буквальном смысле перестраивать собственное мышление. Смешно, но современные принципы линейной разработки отрицающие механизмы глобальных переменных и безусловных переходов - в итоге очень сильно усложнили у новичков понимание рантайма и зависимых состояний..
Anonymous #3585174 Уровень 33
15 июля 2025
like
Виктор Уровень 26
14 июля 2025
Насыпьте смысла к третьей задаче) кто может. Показать что можно проводить действия с данными пока они идут от одного потока к другому?
killjoyme Уровень 29
21 июля 2025
Как по мне, в данной задаче потоки просто прикручены для галочки и не более
Сергей Тыщенко Уровень 29
29 сентября 2025
Не думаю. Скорее хотели показать что у конвейера потоков нет точки/точек состояния. Данные не "идут" из источника в приемник. Данные источника моментально отражаются в приемнике. Можно в любой момент поменять данные источника и данные приемника одновременно изменятся...
Aura Уровень 23
2 июня 2025
"Как ты уже знаешь, BufferedWriter не работает с байтами", это уже смешно =)
Anton Уровень 28
5 июня 2025
я тоже не нашел этого в лекции ))
Сергей Сак Уровень 16
12 июля 2025
Они курс переделали, лекции местами многие поменяли, что-то из курса выкинули. А перепроверить то, что было написано, не удосужились
Aura Уровень 23
13 июля 2025
Ну оно и понятно, я им еще в июне написал, чтоб обратили внимание на эти 2 лекции, ввод и вывод, там рейтинг у задач и лекций нулевый, сказали. что передали все тех. персоналу, но скорее всего забьют болт на это, грубо говоря этот 16 уровень - тест проверка для тех, кто не прям горит Java
Mandarinka1008 Уровень 17
5 мая 2025
честно говоря, считаю последнюю задачу совершенно идиотской, так как условие "развернуть переданную строку с помощью поля outputStream (то есть, outputStream должен хранить в себе перевернутую строку) и вывести в консоли" не выполняется ни в ответе со StringBulder, ни в одном из ответов в комментариях. Нигде разворот не происходит " с помощью поля outputStream", везде просто происходит копирование в другой объект, там разворот и копирование назад. Ну и еще используется .reset про который ни слова не сказано нигде в уроке