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, и он может использоваться в цепочках из объектов-потоков. Довольно полезное свойство на практике.