Перейти к основному содержимому

Руководство по Java OutputStream

· 8 мин. чтения

1. Обзор

В этом руководстве мы подробно рассмотрим Java-класс OutputStream . Выходной поток — это абстрактный класс. Он служит суперклассом для всех классов, представляющих выходной поток байтов.

Мы рассмотрим, что означают такие слова, как «выход» и «поток», более подробно по ходу дела.

2. Краткое введение в Java IO

OutputStream является частью Java IO API , который определяет классы, необходимые для выполнения операций ввода-вывода в Java. Все они упакованы в пространство имен java.io. Это один из основных пакетов, доступных в Java, начиная с версии 1.0.

Начиная с Java 1.4, у нас также есть Java NIO, упакованный в пространство имен java.nio , что позволяет выполнять неблокирующие операции ввода и вывода. Однако в этой статье мы сосредоточимся на ObjectStream как части Java IO.

Подробности, связанные с Java IO и Java NIO, можно найти здесь .

2.1. Ввод и вывод

Java IO в основном предоставляет механизм для чтения данных из источника и записи данных в место назначения . Вход представляет собой источник, а выход представляет здесь пункт назначения.

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

2.2. Потоки

Java IO предоставляет концепцию потоков, которая в основном представляет собой непрерывный поток данных . Потоки могут поддерживать множество различных типов данных, таких как байты, символы, объекты и т. д.

Более того, подключение к источнику или месту назначения — это то, что представляет собой поток. Следовательно, они поступают либо как InputStream , либо как OutputStream соответственно.

3. Интерфейсы OutputStream

OutputStream реализует набор интерфейсов, которые придают своим подклассам определенный характер. Давайте быстро пройдемся по ним.

3.1. Закрываемый

Интерфейс Closeable предоставляет метод close() , который обрабатывает закрытие источника или назначения данных. Каждая реализация OutputStream должна предоставлять реализацию этого метода. Здесь они могут выполнять действия по высвобождению ресурсов.

3.2. AutoCloseable

Интерфейс AutoCloseable также предоставляет метод close() с поведением, сходным с таковым в Closeable . Однако в этом случае метод close() вызывается автоматически при выходе из блока try-with-resource.

Подробнее о try-with-resource можно прочитать здесь .

3.3. Смываемый

Интерфейс Flushable предоставляет метод, называемый flush() , который обрабатывает сброс данных в пункт назначения.

Конкретная реализация OutputStream может выбрать буферизацию ранее записанных байтов для оптимизации, но вызов flush() заставляет ее немедленно записать в место назначения .

4. Методы в OutputStream

OutputStream имеет несколько методов, которые каждый реализующий класс должен реализовать для соответствующих типов данных.

Это помимо методов close() и flush() , которые он наследует от интерфейсов Closeable и Flushable .

4.1. написать (инт б)

Мы можем использовать этот метод для записи одного определенного байта в OutputStream . Поскольку аргумент «int» состоит из четырех байтов, по контракту записывается только первый младший байт, а оставшиеся три старших байта игнорируются:

public static void fileOutputStreamByteSingle(String file, String data) throws IOException {
byte[] bytes = data.getBytes();
try (OutputStream out = new FileOutputStream(file)) {
out.write(bytes[6]);
}
}

Если мы вызовем этот метод с данными как «Hello World!», в результате мы получим файл со следующим текстом:

W

Это, как мы видим, седьмой символ строки с индексом sixth.

4.2. запись (byte[] b, int off, int length)

Эта перегруженная версия метода write() предназначена для записи подпоследовательности массива байтов в OutputStream .

Он может записывать число байтов «length» из массива байтов, как указано аргументом, начиная со смещения, определяемого «off», в OutputStream:

public static void fileOutputStreamByteSubSequence(
String file, String data) throws IOException {
byte[] bytes = data.getBytes();
try (OutputStream out = new FileOutputStream(file)) {
out.write(bytes, 6, 5);
}
}

Если мы теперь вызовем этот метод с теми же данными, что и раньше, мы получим следующий текст в нашем выходном файле:

World

Это подстрока наших данных, начинающаяся с индекса пять и состоящая из пяти символов.

4.3. написать (байт [] б)

Это еще одна перегруженная версия метода write() , которая может записывать весь массив байтов, как указано аргументом в OutputStream .

Это имеет тот же эффект, что и вызов write(b, 0, b.lengh) :

public static void fileOutputStreamByteSequence(String file, String data) throws IOException {
byte[] bytes = data.getBytes();
try (OutputStream out = new FileOutputStream(file)) {
out.write(bytes);
}
}

Когда мы вызываем этот метод сейчас с теми же данными, у нас есть вся строка в нашем выходном файле:

Hello World!

5. Прямые подклассы OutputStream

Теперь мы обсудим некоторые из прямых известных подклассов OutputStream , которые индивидуально представляют определенный тип данных, для которого OutputStream они определяют.

Они определяют свои собственные методы, кроме реализации унаследованных от OutputStream .

Мы не будем вдаваться в подробности этих подклассов.

5.1. FileOutputStream

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

Мы уже рассмотрели различные методы в FileOutputStream в предыдущем разделе.

5.2. ByteArrayOutputStream

ByteArrayOutputStream — это реализация OutputStream , которая может записывать данные в массив байтов . Буфер продолжает расти по мере того, как ByteArrayOutputStream записывает в него данные.

Мы можем оставить начальный размер буфера по умолчанию равным 32 байтам или задать конкретный размер с помощью одного из доступных конструкторов.

Здесь важно отметить, что метод close() практически не действует. Другие методы в ByteArrayOutputStream можно безопасно вызывать даже после вызова функции close() .

5.3. ФильтрВыходнойПоток

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

Некоторыми примерами FilterOutputStream являются BufferedOutputStream , CheckedOutputStream , CipherOutputStream , DataOutputStream , DeflaterOutputStream , DigestOutputStream , InflaterOutputStream , PrintStream .

5.4. ObjectOutputStream

ObjectOutputStream может записывать примитивные типы данных и графики объектов Java в место назначения. Мы можем создать ObjectOutputStream , используя существующий OutputStream для записи в определенное место назначения, например в файл.

Обратите внимание, что объекты должны реализовывать Serializable для ObjectOutputStream , чтобы записывать их в место назначения. Вы можете найти более подробную информацию о сериализации Java здесь .

5.5. PipedOutputStream

PipedOutputStream полезен для создания коммуникационного канала . PipedOutputStream может записывать данные, которые может читать подключенный PipedInputStream .

PipedOutputStream имеет конструктор для соединения с PipedInputStream . В качестве альтернативы, мы можем сделать это позже, используя метод connect() , предоставленный в PipedOutputStream . ``

6. Буферизация выходного потока

Операции ввода и вывода обычно связаны с относительно дорогостоящими операциями, такими как доступ к диску, сетевая активность и т. д. Выполнение этого часто может сделать программу менее эффективной.

У нас есть «буферизованные потоки» данных в Java для обработки этих сценариев. Вместо этого BufferedOutputStream записывает данные в буфер, который реже сбрасывается в место назначения , когда буфер заполняется или вызывается метод flush() .

BufferedOutputStream расширяет рассмотренный ранее FilterOutputStream и оборачивает существующий OutputStream для записи в пункт назначения:

public static void bufferedOutputStream(
String file, String ...data) throws IOException {

try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
for(String s : data) {
out.write(s.getBytes());
out.write(" ".getBytes());
}
}
}

Важно отметить, что каждый вызов write() для каждого аргумента данных только записывает в буфер и не приводит к потенциально дорогостоящему вызову File.

В приведенном выше случае, если мы вызовем этот метод с данными как «Hello», «World!», это приведет к тому, что данные будут записаны в файл только тогда, когда код выйдет из блока try-with-resources, который вызывает метод close () в BufferedOutputStream .

В результате получается выходной файл со следующим текстом:

Hello World!

7. Написание текста с помощью OutputStreamWriter

Поток байтов, как обсуждалось ранее, представляет необработанные данные, которые могут быть набором текстовых символов. Теперь мы можем получить массив символов и сами выполнить преобразование в массив байтов:

byte[] bytes = data.getBytes();

Java предоставляет удобные классы для преодоления этого разрыва. В случае OutputStream этим классом является OutputStreamWriter . OutputStreamWriter оборачивает OutputStream и может напрямую записывать символы в нужное место назначения .

Мы также можем дополнительно предоставить OutputStreamWriter набор символов для кодирования:

public static void outputStreamWriter(String file, String data) throws IOException {
try (OutputStream out = new FileOutputStream(file);
Writer writer = new OutputStreamWriter(out,"UTF-8")) {
writer.write(data);
}
}

Теперь, как мы видим, нам не нужно выполнять преобразование массива символов в массив байтов перед использованием FileOutputStream. OutputStreamWriter делает это удобно для нас .

Неудивительно, что когда мы вызываем вышеуказанный метод с такими данными, как «Hello World!», это приводит к файлу с текстом в виде:

Hello World!

8. Заключение

В этой статье мы обсудили абстрактный класс Java OutputStream . Мы рассмотрели интерфейсы, которые он реализует, и методы, которые он предоставляет.

Затем мы обсудили некоторые из подклассов OutputStream, доступных в Java. Наконец-то мы поговорили о буферизации и символьных потоках.

Как всегда, код примеров доступен на GitHub .