Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Раздел 11. Работа с файлами, I/O и сетью (NIO.2)

Глава 1. Классический Java I/O (java.io)

Правильная работа с текстовыми файлами: InputStreamReader и OutputStreamWriter

Классы FileReader и FileWriter — удобные обертки, но содержат фундаментальный архитектурный дефект: они используют кодировку платформы по умолчанию, определяемую системным свойством file.encoding. Это свойство зависит от операционной системы, locale пользователя и параметров запуска JVM.
// Антипаттерн: неявная кодировка платформы
try (FileReader reader = new FileReader("text.txt")) {
// На Windows может использоваться windows-1251
// На Linux — UTF-8
// На macOS — UTF-8
// Результат непредсказуем при переносе между системами
}


Эта неопределенность делает FileReader/FileWriter непригодными для переносимого кода. Файл, записанный на одной системе, может быть искажен при чтении на другой. Современная практика рекомендует явное указание кодировки UTF-8 для всех текстовых операций.


InputStreamReader: декодирование байтов в символы

InputStreamReader — мост от байтовых потоков к символьным. Он читает байты из InputStream и декодирует их в символы char с использованием указанной кодировки.
public InputStreamReader(InputStream in, Charset cs)

Конструктор принимает InputStream — источник сырых байтов, и Charset — правило декодирования. StandardCharsets.UTF_8 — предопределенная константа, исключающая риск неподдерживаемой кодировки.

Путь данных через память JVM
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream("text.txt"), StandardCharsets.UTF_8)) {

int charValue;
while ((charValue = reader.read()) != -1) {
System.out.print((char) charValue);
}
}


Путь данных детально:
[Файл на диске: байты UTF-8]
-> [Page Cache ОС: байты файла]
-> [FileInputStream.read(byte[]) — системный вызов]
-> [Нативный буфер userspace]
-> [JNI: копирование в byte[] внутри InputStreamReader]
-> [InputStreamReader: накопление байт во внутреннем ByteBuffer]
-> [CharsetDecoder UTF-8: конечный автомат декодирования]
-> [CharBuffer: накопление декодированных символов]
-> [read(): возврат char как int]
-> [System.out.print(char): кодирование в байты платформы]


Внутреннее устройство InputStreamReader из OpenJDK:
// Упрощенная структура
public class InputStreamReader extends Reader {
private final StreamDecoder sd; // Делегат декодирования

// StreamDecoder содержит:
// - ByteBuffer: входные байты из потока
// - CharBuffer: выходные символы
// - CharsetDecoder: конечный автомат кодировки
}

StreamDecoder — внутренний класс, выполняющий реальную работу. Он управляет ByteBuffer для входных байт и CharBuffer для выходных символов. Буферы создаются при конструировании и живут до закрытия потока.


Механика декодирования UTF-8

UTF-8 — кодировка переменной длины. Декодер должен обрабатывать мультибайтовые последовательности, разбитые между блоками чтения:
// Сценарий: 3-байтовый символ '世' (U+4E16, E4 B8 96) разбит между чтениями

// Первое чтение: получены байты [..., E4, B8] — неполная последовательность
// StreamDecoder сохраняет E4 B8 во внутреннем ByteBuffer, возвращает 0 символов

// Второе чтение: получен байт [96, ...] — завершение последовательности
// StreamDecoder комбинирует E4 B8 96, декодирует в U+4E16, возвращает '世'

Это требует внутреннего состояния в StreamDecoder: неполные байтовые последовательности сохраняются между вызовами read(). Буфер для этих остатков — часть ByteBuffer в heap.


OutputStreamWriter: кодирование символов в байты

OutputStreamWriter — зеркальный мост: принимает символы char или String, кодирует их в байты согласно указанной кодировке, и записывает в OutputStream.
public OutputStreamWriter(OutputStream out, Charset cs)


Путь данных через память JVM
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream("output.txt"), StandardCharsets.UTF_8)) {

writer.write("Hello, 世界! 🌍");
}


Путь данных детально:
[Java-код: String "Hello, 世界! 🌍"]
-> [String.toCharArray() или прямой доступ к внутреннему char[]]
-> [OutputStreamWriter.write(char[], off, len)]
-> [StreamEncoder: кодирование char -> байты]
-> [CharsetEncoder UTF-8: конечный автомат]
-> [ByteBuffer: накопление закодированных байт]
-> [FileOutputStream.write(byte[]): системный вызов]
-> [Нативный буфер userspace]
-> [Ядро ОС: syscall write]
-> [Page Cache: dirty pages]
-> [Фоновая запись на диск]

StreamEncoder — внутренний аналог StreamDecoder. Он управляет CharBuffer для входных символов и ByteBuffer для выходных байт. При вызове write(String) символы сначала копируются в CharBuffer, затем кодер преобразует их в байты UTF-8.


Кодирование и размер выходных данных

Строка "Hello, 世界! 🌍" содержит:
8 символов ASCII: 8 байт UTF-8
'世' (U+4E16): 3 байта UTF-8
'界' (U+754C): 3 байта UTF-8
'🌍' (U+1F30D): суррогатная пара, 4 байта UTF-8

Итого: 18 байт для 11 кодовых точек, 12 char в Java
StreamEncoder должен обрабатывать суррогатные пары корректно: старший суррогат без младшего не является валидным символом и должен либо ждать дополнительных данных, либо генерировать замену (replacement character U+FFFD).


#Java #для_новичков #beginner #IO #NIO #InputStreamReader #OutputStreamWriter
👍3