Получить доступ

Потоки ввода-вывода: байты, символы, буферы

Байтовые и символьные потоки, кодировки и Charset, буферизация и системные вызовы, декоратор-паттерн и современный файловый API.

Байтовые и символьные потоки

В java.io две параллельные иерархии. InputStream/OutputStream работают с сырыми байтами — для бинарных данных (картинки, архивы, протоколы). Reader/Writer работают с символами и всегда подразумевают кодировку — для текста.

Мост между мирами — InputStreamReader/OutputStreamWriter: они превращают байты в символы и обратно по заданному Charset. Путать байты и символы — корень множества багов с «кракозябрами».

try (Reader r = new InputStreamReader(
        Files.newInputStream(path), StandardCharsets.UTF_8)) {
    // байты файла → символы в явной кодировке
}

Кодировки и Charset

Текст — это не байты: один и тот же символ кодируется разным числом байт в разных кодировках (UTF-8, UTF-16, Windows-1251). Поэтому при переходе байты↔символы кодировку нужно знать; читать чужой текст «как получится» нельзя.

Правило: всегда указывайте Charset явно (StandardCharsets.UTF_8). Историческая ловушка — дефолтная кодировка платформы (file.encoding), из-за которой код вёл себя по-разному на разных машинах; с Java 18 дефолт файлов унифицирован на UTF-8, но привычка задавать кодировку явно остаётся правильной.

Буферизация и системные вызовы

Каждое «сырое» чтение/запись по одному байту/символу часто оборачивается системным вызовом к ОС — это дорого. Buffered-обёртки (BufferedInputStream/BufferedReader и парные на запись) копят данные в буфере в памяти и обращаются к ОС крупными порциями, что обычно и даёт главный прирост скорости.

Буферизация не меняет формат данных — только укрупняет обмен. BufferedReader к тому же добавляет удобный readLine(). На запись важно не забыть flush()/close(), иначе хвост данных останется в буфере.

Декоратор-паттерн java.io

java.io — хрестоматийный пример паттерна Decorator: потоки оборачивают друг друга, и каждый слой добавляет одну возможность (буферизацию, перекодировку, сжатие). Поэтому конструкции выглядят как «матрёшка».

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

var in = new BufferedReader(
    new InputStreamReader(
        new GZIPInputStream(Files.newInputStream(path)), // 1) распаковка
        StandardCharsets.UTF_8));                         // 2) байты→символы, 3) буфер

Современный файловый API

Для типовых задач не нужно вручную городить потоки: класс Files даёт Files.readString(path), readAllLines, lines (ленивый Stream<String> — обязательно закрывать), write — всё с явным Charset. Путь представляют через Path/Paths, а не устаревший File.

Ручные потоки остаются для больших данных, нестандартных форматов и стриминга; для «прочитать конфиг/файл целиком» современный Files короче и безопаснее.

Как отвечать на собеседовании

Разделите байтовые (InputStream/OutputStream) и символьные (Reader/Writer) потоки и роль моста InputStreamReader+Charset, объясните, зачем явный UTF-8 (и про дефолт платформы до Java 18). Свяжите буферизацию с экономией системных вызовов и распознайте в java.io паттерн Decorator. Бонус — современный Files/Path для простых случаев.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем