Буферизированный ввод и вывод

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

Зачем нужен ещё один слой вокруг потока

Частое обращение к файлу или другому внешнему источнику может быть заметно дороже, чем работа с памятью. Буферизированные потоки уменьшают количество таких мелких операций: они читают или накапливают данные блоками во внутреннем буфере, а код пользователя работает с привычным API.

Для байтов есть BufferedInputStream и BufferedOutputStream, для символов — BufferedReader и BufferedWriter. Обычно они оборачивают уже существующий поток или reader/writer.

try (InputStream in = new BufferedInputStream(
        new FileInputStream("photo.jpg"))) {
    byte[] header = in.readNBytes(16);
}

Снаружи это всё ещё InputStream, поэтому остальной код может не знать, есть внутри буфер или нет. Это хороший пример композиции потоков: дополнительные возможности добавляются обёрткой.

BufferedReader и чтение строк

Для текста буферизация особенно заметна благодаря BufferedReader: он умеет читать данные построчно через readLine(). Возвращённая строка не содержит символ перевода строки, а null означает конец данных.

try (BufferedReader reader = new BufferedReader(
        new FileReader("app.log", StandardCharsets.UTF_8))) {
    String line = reader.readLine();
    System.out.println(line);
}

Когда нужен flush()

Буферизированный вывод может некоторое время держать записанные данные в памяти. Метод flush() просит передать накопленные данные дальше по цепочке, не закрывая сам поток. Это важно, когда другой участник должен увидеть вывод до завершения работы ресурса.

try (BufferedWriter writer = new BufferedWriter(
        new FileWriter("status.txt", StandardCharsets.UTF_8))) {
    writer.write("READY");
    writer.flush();
}
Не вызывай flush() после каждой маленькой записи без причины. Слишком частый принудительный сброс уменьшает пользу буферизации. При обычном закрытии потока оставшиеся данные передаются дальше автоматически.