InputStreamReader: примеры (JAVA)
InputStreamReader(InputStream in, String charsetName)Общее описание InputStreamReader
Класс InputStreamReader из пакета java.io представляет собой адаптер между байтовыми потоками и символьными. Он декодирует входящие байты в символы с использованием заданной кодировки (Charset) или системной кодировки по умолчанию. Типичное применение: чтение текстовых данных из InputStream (файловый, сетевой, стандартный ввод и т. п.) с правильной интерпретацией байтов как символов.
Основные конструкторы и их аргументы:
InputStreamReader(InputStream in)- принимает поток байтов; используется системная кодировка (получается черезCharset.defaultCharset()).InputStreamReader(InputStream in, Charset cs)- принимаетCharset, наиболее безопасный способ явно задать кодировку.InputStreamReader(InputStream in, CharsetDecoder dec)- принимает объектCharsetDecoder, позволяющий настроить поведение при ошибках декодирования (например,CodingErrorAction.REPLACEилиREPORT).- Исторически существовал конструктор
InputStreamReader(InputStream in, String charsetName), который может бросатьUnsupportedEncodingExceptionпри неверном имени кодировки; в современных реализациях рекомендуется использоватьCharset.
Основные методы и возвращаемые значения:
int read()- чтение одного символа, возвращает значение символа в видеint(0..65535) или-1при достижении конца потока.int read(char[] cbuf, int off, int len)- чтение в массив символов, возвращает количество прочитанных символов или-1при EOF.int read(CharBuffer target)- реализация из классаReader, возвращает количество записанных символов или-1.long skip(long n)- пропуск символов, возвращает реально пропущенное количество.boolean ready()- сообщает, доступно ли чтение без блокировки (не гарантирует всех данных).void close()- закрытие оборачиваемогоInputStreamи освобождение ресурсов.boolean markSupported()- обычно возвращаетfalse(метка и сброс по умолчанию не поддерживаются).String getEncoding()- возвращает имя используемой кодировки илиnull, если кодировка неизвестна.
Особенности поведения:
- При неявной системной кодировке результат зависит от окружения (локали, системной переменной
file.encoding), поэтому явное указаниеCharsetснижает риск ошибок. - Декодирование многобайтовых символов может происходить с частичным чтением байтов между вызовами
read; внутренняя логика обеспечивает корректную сборку многобайтовых последовательностей, но поведение при поврежденных последовательностях зависит отCharsetDecoder. - Для эффективного построчного чтения обычно используют обертку
new BufferedReader(new InputStreamReader(...)).
Краткие примеры использования
Примеры ниже демонстрируют ключевые варианты создания и чтения с выводом результата.
1) Чтение с явным указанием кодировки UTF-8:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Example1 {
public static void main(String[] args) throws Exception {
byte[] bytes = "Привет".getBytes(StandardCharsets.UTF_8);
try (InputStream in = new ByteArrayInputStream(bytes);
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8)) {
char[] buf = new char[16];
int n = isr.read(buf);
System.out.println(n);
System.out.println(new String(buf, 0, n));
}
}
}
6 Привет
2) Чтение с неправильной кодировкой (байты в UTF-8, чтение как ISO-8859-1) - результат будет искажён:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
public class Example2 {
public static void main(String[] args) throws Exception {
byte[] bytes = "Привет".getBytes(StandardCharsets.UTF_8);
try (InputStream in = new ByteArrayInputStream(bytes);
InputStreamReader isr = new InputStreamReader(in, Charset.forName("ISO-8859-1"))) {
char[] buf = new char[32];
int n = isr.read(buf);
System.out.println(n);
System.out.println(new String(buf, 0, n));
}
}
}
12 Ïðèâåò
3) Использование CharsetDecoder с режимом REPORT - при повреждении последовательности возникнет исключение при чтении:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.*;
public class Example3 {
public static void main(String[] args) {
// Некорректная UTF-8 последовательность: 0xC3 0x28
byte[] bad = new byte[] {(byte)0xC3, (byte)0x28};
try (InputStream in = new ByteArrayInputStream(bad)) {
CharsetDecoder dec = StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
InputStreamReader isr = new InputStreamReader(in, dec);
char[] buf = new char[10];
isr.read(buf); // ожидание IOException/CharacterCodingException
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
}
java.io.IOException: Malformed input or unmappable character
at java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:281)
... (стек вызовов зависит от JVM)
4) Часто применяемый шаблон: построчное чтение с буферизацией:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Example4 {
public static void main(String[] args) throws Exception {
String text = "строка1\nстрока2";
try (InputStream in = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
}
строка1 строка2
5) Получение имени кодировки:
InputStreamReader isr = new InputStreamReader(System.in, StandardCharsets.UTF_8);
System.out.println(isr.getEncoding());
isr.close();
UTF-8
Похожие инструменты в Java
В экосистеме Java имеются классы и утилиты, выполняющие близкие задачи:
- BufferedReader - не декодер сам по себе, но часто используется поверх
InputStreamReaderдля эффективного построчного чтения. Предпочтителен при чтении строк и при необходимости буферизации. - FileReader - наследник
Reader, исторически создаёт поток символов из файла, применяя системную кодировку; использованиеnew InputStreamReader(new FileInputStream(file), Charset)даёт более явный контроль над кодировкой. - Scanner - удобен для парсинга токенов и примитивных типов; внутри может использовать декодирование, но даёт дополнительную функциональность (разбиение по шаблонам) и более высокую накладную стоимость.
- java.nio.channels.Channels.newReader и NIO Streams - альтернативный API для обёртывания каналов; полезен при работе с неблокирующими каналами и при интеграции с NIO.
- CharsetDecoder и связанные API - при необходимости полного контроля над процессом декодирования (обработка ошибок, буферизация, состояние декодера).
Рекомендации по выбору:
- для простого и контролируемого декодирования рекомендуется
InputStreamReaderс явнымCharsetилиCharsetDecoder; - для построчного чтения добавить
BufferedReaderповерхInputStreamReader; - если требуется разбор токенов или чисел - рассмотреть
Scanner, но помнить о производительности; - при взаимодействии с NIO каналами предпочтительнее API из
java.nioиChannels.newReader.
Аналоги в других языках и отличия
Краткие соответствия и отличия при работе с байтами и декодированием в популярных языках.
- Python: модуль
ioпредоставляетTextIOWrapper, оборачивающий бинарный поток. Поведение похоже: явное указание кодировки через аргументencoding, настройка ошибок черезerrors.
import io
from io import BytesIO
b = "Привет".encode('utf-8')
with io.TextIOWrapper(BytesIO(b), encoding='utf-8') as t:
print(t.read())
Привет
- JavaScript (в браузере и в Node.js): класс
TextDecoderдекодирует Uint8Array в строку. Отличие: API ориентирован на байтовые буферы, не на поток ввода-вывода в стиле Reader.
// Node.js или браузер
const bytes = new TextEncoder().encode('Привет');
const dec = new TextDecoder('utf-8');
console.log(dec.decode(bytes));
Привет
- C#: класс
StreamReaderвыполняет роль, близкую к InputStreamReader; поддерживаются явные кодировки черезEncodingи параметры обработки ошибок.
using System;
using System.IO;
using System.Text;
class P {
static void Main() {
byte[] bytes = Encoding.UTF8.GetBytes("Привет");
using var ms = new MemoryStream(bytes);
using var sr = new StreamReader(ms, Encoding.UTF8);
Console.WriteLine(sr.ReadToEnd());
}
}
Привет
- PHP: функции работы со строками и потоки; для декодирования часто используют
mb_convert_encodingили обёртки потоков с фильтрами. Прямого эквивалента класса-обёртки для байтов в символы в стандартной библиотеке в точности как Java нет, но задачи решаются сочетанием функций и stream wrappers.
$bytes = "Привет"; // в PHP строки бинарно-ориентированы
echo mb_convert_encoding($bytes, 'UTF-8', 'UTF-8');
Привет
- Go: стандартная библиотека использует UTF-8 по умолчанию для строк; для работы с другими кодировками применяется пакет
golang.org/x/text/encoding, где есть декодеры и обёртки для потоков.
package main
import (
"bytes"
"fmt"
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/transform"
"io/ioutil"
)
func main() {
b := []byte{0x50, 0xF0} // пример байтов в другой кодировке
r := transform.NewReader(bytes.NewReader(b), charmap.ISO8859_1.NewDecoder())
out, _ := ioutil.ReadAll(r)
fmt.Println(string(out))
}
P�
Ключевые отличия от Java:
- в Python и C# есть аналогичные обёртки, в JavaScript используется низкоуровневый декодер для буферов;
- в Go и PHP требуется внешняя библиотека/функции для работы с нестандартными кодировками;
- везде рекомендуется явное указание кодировки, чтобы избежать зависимости от системных настроек.
Типичные ошибки и их симптомы
Ниже перечислены распространённые проблемы при работе с InputStreamReader и небольшие примеры.
1) Неверная кодировка приводит к искажениям или замене символов знаком вопроса/символом замены:
byte[] utf8 = "Привет".getBytes(StandardCharsets.UTF_8);
try (InputStreamReader r = new InputStreamReader(new ByteArrayInputStream(utf8), Charset.forName("ISO-8859-1"))) {
char[] buf = new char[32];
int n = r.read(buf);
System.out.println(new String(buf, 0, n));
}
Ïðèâåò
2) Неправильная обработка повреждённых последовательностей: при использовании декодера с CodingErrorAction.REPLACE появится символ замены; с REPORT будет исключение. Пример с REPORT показан в разделе примеров, результат - IOException/CharacterCodingException.
3) Забытая закрытия потока - утечка ресурсов. Закрытие через try-with-resources гарантирует освобождение.
4) Ожидание read() до EOF при блокирующих потоках (сокеты, System.in) - чтение может блокировать до тех пор, пока данные не станут доступны или не будет закрыт поток. Пример сетевого ожидания требует асинхронной логики или таймаута.
5) Ожидание меток или reset у потока, где markSupported() возвращает false - попытки вызвать mark/reset без проверки приведут к сбою.
6) Невнимание к BOM (Byte Order Mark): при наличии BOM в начале потока символ BOM попадёт в результат как символ \uFEFF, если не выполнена предварительная обработка.
byte[] withBom = new byte[] {(byte)0xEF, (byte)0xBB, (byte)0xBF}.length > 0 ?
new byte[] {(byte)0xEF, (byte)0xBB, (byte)0xBF, 'A'} : new byte[] {'A'};
// при чтении BOM станет первым символом (\uFEFF)
(в зависимости от содержимого) \uFEFFA
7) Использование устаревших конструкторов с именем кодировки - возможен UnsupportedEncodingException при опечатке в строковом имени кодировки.
Изменения в реализации и рекомендации
За последние версии Java сам класс InputStreamReader не подвергался значительным функциональным изменениям. Основные рекомендации и эволюция касаются использования API:
- предпочтение конструктора с
CharsetилиCharsetDecoderвместо строкового имени кодировки для избежанияUnsupportedEncodingExceptionи опечаток; - в новых приложениях рекомендуется явное указание кодировки (например,
StandardCharsets.UTF_8) вместо полагания на системную по умолчанию; - поведение при ошибках декодирования осталось управляемым через
CharsetDecoderиCodingErrorAction; - замечание: системная кодировка может зависеть от параметров JVM и окружения, поэтому переносимость без явного указания кодировки ограничена.
В целом API стабильно и обратимо совместимо в современных релизах.
Расширенные и неочевидные сценарии использования
1) Конвертация между кодировками в потоке (пример: CP1251 -> UTF-8):
import java.io.*;
import java.nio.charset.Charset;
public class ConvertEncoding {
public static void main(String[] args) throws Exception {
byte[] cp1251 = "Привет".getBytes(Charset.forName("CP1251"));
try (InputStream in = new ByteArrayInputStream(cp1251);
InputStreamReader isr = new InputStreamReader(in, Charset.forName("CP1251"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(baos, Charset.forName("UTF-8"))) {
char[] buf = new char[64];
int n;
while ((n = isr.read(buf)) != -1) {
osw.write(buf, 0, n);
}
osw.flush();
System.out.println(new String(baos.toByteArray(), Charset.forName("UTF-8")));
}
}
}
Привет
Пояснение: поток из байтов в CP1251 декодируется в символы, затем символы кодируются в UTF-8 и записываются в байтовый буфер.
2) Обработка повреждённых последовательностей с попыткой восстановления: комбинирование декодера с заменой и логированием позиций.
import java.io.*;
import java.nio.charset.*;
public class RecoveryExample {
public static void main(String[] args) throws Exception {
byte[] bad = new byte[] {(byte)0xD0, (byte)0x9F, (byte)0xC3, (byte)0x28, (byte)0xD1, (byte)0x82};
CharsetDecoder dec = StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.replaceWith("?");
try (InputStream in = new ByteArrayInputStream(bad);
InputStreamReader isr = new InputStreamReader(in, dec)) {
int ch;
StringBuilder sb = new StringBuilder();
while ((ch = isr.read()) != -1) sb.append((char) ch);
System.out.println(sb.toString());
}
}
}
П?(т
Пояснение: повреждённая последовательность заменяется знаком вопроса, что позволяет сохранить позицию и продолжить чтение.
3) Чтение потоковых данных с малыми буферами для проверки устойчивости декодера к разрезанию многобайтовых символов:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class SmallBufferTest {
public static void main(String[] args) throws Exception {
byte[] bytes = "Привет мир".getBytes(StandardCharsets.UTF_8);
try (InputStream in = new ByteArrayInputStream(bytes);
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8)) {
char[] buf = new char[2]; // очень маленький буфер
int n;
while ((n = isr.read(buf)) != -1) {
System.out.print(new String(buf, 0, n));
}
}
}
}
Привет мир
Пояснение: даже при разрезании байтового потока на маленькие порции декодер корректно собирает многобайтовые символы.
4) Учет BOM в потоке: демонстрация появления символа BOM в начале при явном чтении:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class BomExample {
public static void main(String[] args) throws Exception {
byte[] bomUtf8 = new byte[] {(byte)0xEF, (byte)0xBB, (byte)0xBF};
byte[] text = "Hello".getBytes(StandardCharsets.UTF_8);
byte[] combined = new byte[bomUtf8.length + text.length];
System.arraycopy(bomUtf8, 0, combined, 0, bomUtf8.length);
System.arraycopy(text, 0, combined, bomUtf8.length, text.length);
try (InputStream in = new ByteArrayInputStream(combined);
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8)) {
int ch = isr.read();
System.out.printf("first char: 0x%04X\n", ch);
char[] rest = new char[10];
int n = isr.read(rest);
System.out.println(new String(rest, 0, n));
}
}
}
first char: 0xFEFF Hello
Пояснение: BOM в UTF-8 представлен как символ U+FEFF и не удаляется автоматически InputStreamReader; при необходимости BOM следует обрабатывать отдельно.
5) Чтение из сокета с контролем таймаута и декодированием (упрощённый пример):
// Псевдокод для сервера/клиента: важно устанавливать таймаут сокета и использовать InputStreamReader поверх сокетного InputStream
// Socket s = serverSocket.accept();
// s.setSoTimeout(5000);
// try (InputStream in = s.getInputStream();
// InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
// BufferedReader br = new BufferedReader(isr)) {
// String line = br.readLine();
// }
(в зависимости от сетевого взаимодействия - строка или таймаутное исключение)