OutputStreamWriter: примеры (JAVA)

Примеры работы с OutputStreamWriter
Раздел: Потоки ввода-вывода (Streams) символьные
OutputStreamWriter(OutputStream out, String charsetName)

Описание класса OutputStreamWriter

Класс OutputStreamWriter из пакета java.io представляет адаптер, преобразующий поток символов в поток байтов. Он наследуется от Writer и служит для записи символов в поток байтов с указанной кодировкой. Основная идея - принимать символы (UTF-16 в Java) и кодировать их в последовательность байтов, отправляемую в связанный OutputStream.

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

Доступные конструкторы и их параметры:

  • OutputStreamWriter(OutputStream out) - связывает писатель с указанным байтовым потоком с использованием кодировки по умолчанию платформы. Параметр: out - не должен быть null; при попытке использовать null возникает NullPointerException.
  • OutputStreamWriter(OutputStream out, String charsetName) - связывает писатель с указанным потоком и кодировкой по имени. Параметры: out, charsetName. При передаче несуществующей кодировки бросается UnsupportedEncodingException.
  • OutputStreamWriter(OutputStream out, java.nio.charset.Charset cs) - аналогично предыдущему, но кодировка задается объектом Charset. Параметры: out, cs. Null вызывает NullPointerException.

Основные методы и их поведение:

  • void write(int c) - записывает один символ (целое значение) в поток. Возвращаемое значение отсутствует. Может бросать IOException.
  • void write(char[] cbuf, int off, int len) - записывает часть массива символов.
  • void write(String str, int off, int len) - записывает часть строки.
  • Writer append(char c) и перегрузки append(CharSequence) - добавляют символы и возвращают сам объект Writer (обычно OutputStreamWriter), что позволяет цепочку вызовов.
  • void flush() - принудительное преобразование оставшихся символов в байты и запись их в связанный OutputStream. Выбрасывает IOException при ошибке ввода-вывода.
  • void close() - закрывает писатель и связанный поток. Внутри происходит flush, затем закрытие базового потока.
  • String getEncoding() - возвращает имя текущей кодировки (возможно null, если поток закрыт или кодировка неизвестна).

Поведение при невозможности закодировать знак. По умолчанию для большинства кодировок используется поведение замены символа на код символа замены (например, '?'). Для специального управления преобразованием необходимо использовать CharsetEncoder напрямую или другие инструменты.

Взаимодействие с буферизацией и другими обёртками. OutputStreamWriter не обеспечивает большого внутреннего буфера символов; при записи большого объёма рекомендуется оборачивать его в BufferedWriter для уменьшения системных вызовов. Если требуется удобство форматирования, предпочтительнее PrintWriter, но у него иные политики обработки исключений (по умолчанию подавляются).

Короткие примеры использования

Пример 1. Запись в память в кодировке UTF-8 и вывод результата.

import java.io.*;
import java.nio.charset.StandardCharsets;

ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (OutputStreamWriter osw = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
    osw.write("Привет");
    osw.flush();
}
String s = baos.toString("UTF-8");
System.out.println(s);
Привет

Пример 2. Кодировка, не поддерживающая символы, приводит к замене символов.

import java.io.*;
import java.util.Arrays;

ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (OutputStreamWriter osw = new OutputStreamWriter(baos, "ISO-8859-1")) {
    osw.write("Привет");
    osw.flush();
}
byte[] bytes = baos.toByteArray();
System.out.println(Arrays.toString(bytes));
System.out.println(new String(bytes, "ISO-8859-1"));
[63, 63, 63, 63, 63, 63]
??????

Пример 3. Писатель в стандартный вывод с указанием кодировки.

import java.io.*;
import java.nio.charset.StandardCharsets;

try (OutputStreamWriter osw = new OutputStreamWriter(System.out, StandardCharsets.UTF_8)) {
    osw.write("Line 1\nLine 2\n");
    osw.flush();
}
Line 1
Line 2

Похожие механизмы в Java и их особенности

  • FileWriter - упрощённая обёртка над OutputStreamWriter, использующая кодировку по умолчанию платформы. Удобен для быстрого кода, но нежелателен при необходимости контроля кодировки.
  • BufferedWriter - добавляет буферизацию для Writer, повышая производительность при частых операциях записи. Часто используется вместе с OutputStreamWriter.
  • PrintWriter - удобные методы форматирования и печати; имеет опцию автосброса. По умолчанию подавляет исключения ввода-вывода, что может скрывать ошибки.
  • Files.newBufferedWriter(Path, Charset) (NIO) - современный способ создания писателя с указанием кодировки и опций открытия файла. Предпочтителен для файлового ввода-вывода в новых проектах.
  • Channels.newWriter - создаёт Writer поверх канала, полезен при работе с NIO-каналами.

Выбор зависит от требований: нужен контроль кодировки - использовать OutputStreamWriter или NIO-эквиваленты; требуется буферизация - добавлять BufferedWriter; нужны удобные методы форматирования - применять PrintWriter.

Аналоги в других языках и отличия

PHP

$fp = fopen('php://memory', 'r+');
fwrite($fp, mb_convert_encoding('Привет', 'UTF-8', 'UTF-8'));
rewind($fp);
echo stream_get_contents($fp);
Привет

Отличия: в PHP кодировка управляется внешними функциями, нет встроенного адаптера char->byte как в Java.

JavaScript (Node.js)

const fs = require('fs');
fs.writeFileSync('out.txt', 'Привет', {encoding: 'utf8'});
console.log(Buffer.from('Привет', 'utf8').toString());
Привет

Python

with open('out.txt', 'w', encoding='utf-8') as f:
    f.write('Привет')
print('Привет')
Привет

C#

using System.IO;
using System.Text;

var ms = new MemoryStream();
using (var sw = new StreamWriter(ms, Encoding.UTF8, 1024, true)) {
    sw.Write("Привет");
    sw.Flush();
}
ms.Position = 0;
using (var sr = new StreamReader(ms, Encoding.UTF8)) Console.WriteLine(sr.ReadToEnd());
Привет

Go

package main
import (
    "bufio"
    "fmt"
    "os"
)
func main() {
    f, _ := os.Create("out.txt")
    w := bufio.NewWriter(f)
    fmt.Fprint(w, "Привет")
    w.Flush()
}
(файл out.txt содержит "Привет")

Kotlin

import java.io.*
import java.nio.charset.StandardCharsets

val baos = ByteArrayOutputStream()
OutputStreamWriter(baos, StandardCharsets.UTF_8).use { it.write("Привет") }
println(String(baos.toByteArray(), StandardCharsets.UTF_8))
Привет

Lua

local f = io.open('out.txt', 'w')
f:write('Привет')
f:close()
print('Привет')
Привет

SQL

В реляционных СУБД запись текстовых данных в столбцы типа TEXT или NVARCHAR управляется драйвером и настройками соединения; явного аналога адаптера char->byte на стороне базы не требуется. Необходимость кодировки решается параметром соединения и драйвером.

Типичные ошибки при использовании

1. Неправильное имя кодировки

import java.io.*;
OutputStreamWriter osw = new OutputStreamWriter(new ByteArrayOutputStream(), "no-such-charset");
java.io.UnsupportedEncodingException: no-such-charset

2. Запись после закрытия потока

import java.io.*;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(baos);
osw.close();
osw.write('a');
java.io.IOException: Stream closed

3. Непреднамеренная потеря символов при несогласованной буферизации

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

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

ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (OutputStreamWriter osw = new OutputStreamWriter(baos, "US-ASCII")) {
    osw.write("Привет");
    osw.flush();
}
System.out.println(new String(baos.toByteArray(), "US-ASCII"));
??????

5. Подавление исключений при использовании PrintWriter без проверки

При замене OutputStreamWriter на PrintWriter без вызова checkError() часть ошибок записи может быть пропущена.

Изменения и история

Класс OutputStreamWriter присутствует в Java долгое время. Основные моменты истории:

  • В ранних версиях использовалась конструкция с именем кодировки. С появлением NIO (Java 1.4) добавлена поддержка java.nio.charset.Charset, что упростило работу с кодировками и убрало необходимость ловить UnsupportedEncodingException при использовании Charset.
  • В более новых релизах изменений в поведении класса не было много: улучшения касались в основном связанных API (NIO, Files API), благодаря которым появились альтернативы создания писателя.

В последних версиях Java явных изменений контрактов OutputStreamWriter не наблюдается. Рекомендация - использовать API NIO и StandardCharsets для явного указания кодировок.

Расширенные и нетипичные примеры

Пример A. Добавление BOM перед текстом (например, для совместимости с некоторыми редакторами).

Пример java
import java.io.*;
import java.nio.charset.StandardCharsets;

ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Записать UTF-8 BOM вручную
baos.write(new byte[] {(byte)0xEF, (byte)0xBB, (byte)0xBF});
try (OutputStreamWriter osw = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
    osw.write("Привет\n");
    osw.flush();
}
byte[] result = baos.toByteArray();
System.out.println(java.util.Arrays.toString(result));
System.out.println(new String(result, StandardCharsets.UTF_8));
[239, 187, 191, -48, -80, -48, -75, -47, -126, -47, -126, 10]
Привет

Пример B. Использование OutputStreamWriter вместе с BufferedWriter для производительности при большом тексте.

Пример java
import java.io.*;
import java.nio.charset.StandardCharsets;

try (FileOutputStream fos = new FileOutputStream("large.txt");
     OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
     BufferedWriter bw = new BufferedWriter(osw, 16 * 1024)) {
    for (int i = 0; i < 100_000; i++) {
        bw.write("Строка номер " + i + "\n");
    }
}
System.out.println("done");
done

Пример C. Работа с сетевым сокетом и явный flush для протоколов с линиями.

Пример java
import java.net.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

Socket socket = new Socket("example.com", 80);
try (OutputStreamWriter osw = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)) {
    osw.write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n");
    osw.flush();
    // чтение ответа опущено
}
socket.close();
(HTTP-запрос отправлен на сервер)

Пример D. Управление обработкой ошибок при кодировке через явное кодирование через CharsetEncoder.

Пример java
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;

String text = "Привет €"; // содержит символ €
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder()
        .onUnmappableCharacter(CodingErrorAction.REPLACE)
        .onMalformedInput(CodingErrorAction.REPLACE);
try {
    ByteBuffer bb = encoder.encode(CharBuffer.wrap(text));
    baos.write(bb.array(), bb.position(), bb.remaining());
} catch (IOException e) {
    // не произойдёт при записи в ByteArrayOutputStream
}
System.out.println(new String(baos.toByteArray(), java.nio.charset.Charset.forName("ISO-8859-1")));
????? ?

Пример E. Использование NIO-каналов: создание писателя над каналом через Channels.newWriter.

Пример java
import java.io.*;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;

ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel channel = Channels.newChannel(baos);
try (Writer w = Channels.newWriter(channel, StandardCharsets.UTF_8)) {
    w.write("Текст через канал\n");
}
System.out.println(baos.toString("UTF-8"));
Текст через канал

джава OutputStreamWriter function comments

En
OutputStreamWriter A bridge from character streams to byte streams