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

Java класс InputStreamReader - разбор использования
Раздел: Потоки ввода-вывода (Streams) символьные
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):

Пример java
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) Обработка повреждённых последовательностей с попыткой восстановления: комбинирование декодера с заменой и логированием позиций.

Пример java
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) Чтение потоковых данных с малыми буферами для проверки устойчивости декодера к разрезанию многобайтовых символов:

Пример java
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 в начале при явном чтении:

Пример java
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) Чтение из сокета с контролем таймаута и декодированием (упрощённый пример):

Пример java
// Псевдокод для сервера/клиента: важно устанавливать таймаут сокета и использовать 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();
// }
(в зависимости от сетевого взаимодействия - строка или таймаутное исключение)

джава InputStreamReader function comments

En
InputStreamReader A bridge from byte streams to character streams