Символьные потоки

Базовый Java
≈ 2 мин чтения

Текст — это символы, но в файле лежат байты

Для текста удобнее работать не с отдельными байтами, а с символами. Эту модель дают Reader и Writer: первый читает символы, второй записывает их. Но граница между файлом и текстом всё равно проходит через кодировку — правило преобразования байтов в символы и обратно.

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

try (Reader reader = new InputStreamReader(
        new FileInputStream("message.txt"), StandardCharsets.UTF_8)) {
    int ch = reader.read();
    System.out.println((char) ch);
}

InputStreamReader — это мост между двумя мирами: он получает байты из InputStream и декодирует их в символы выбранной кодировкой. В обратную сторону работает OutputStreamWriter.

Reader и Writer работают с char

У символьных потоков есть методы для чтения и записи отдельных символов и массивов char. Как и у байтовых потоков, чтение возвращает специальное значение -1, когда данные закончились.

char[] buffer = new char[1024];

try (Reader reader = new FileReader("message.txt", StandardCharsets.UTF_8)) {
    int read = reader.read(buffer);
    System.out.println(read);
}

Здесь число read снова означает фактическое количество полученных элементов, только теперь речь идёт о символах, а не о сырых байтах.

Запись текста

try (Writer writer = new FileWriter("message.txt", StandardCharsets.UTF_8)) {
    writer.write("Hello, Java!");
}

Writer принимает текст как символы, а конкретная реализация отвечает за превращение их в байты. Это делает намерение кода понятнее: если задача — работать с текстом, символьный API обычно лучше отражает её смысл.

Не преобразовывай текст через случайную кодировку. Один и тот же набор байтов может декодироваться по-разному. Кодировка должна быть частью договорённости о формате данных.