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

Примеры применения flush в потоках вывода
Раздел: Ввод-вывод (I/O) с буферизацией
flush: void

Общее описание

В Java метод flush используется для принудительной передачи накопленных в буфере данных в следующий уровень ввода-вывода. В стандартной библиотеке он объявлен в интерфейсе java.io.Flushable как void flush() и реализуется большинством потоков вывода и писателей: OutputStream и его подклассы (например, BufferedOutputStream), Writer и его реализации (например, BufferedWriter), PrintWriter, PrintStream, ObjectOutputStream и другие.

Назначение метода - опустошить внутренний буфер: переписать накопленные байты/символы в связанный ресурс (вложенный поток, сокет, файловый дескриптор и т.п.). Метод не обязан обеспечивать физическую запись на носитель; для гарантии записи на диск используются дополнительные API (см. раздел альтернатив).

Сигнатуры и поведение:

  • void flush() throws IOException - стандартная сигнатура для потоков ввода-вывода и большинства Flushable-реализаций. Может выбрасывать IOException, если произошла ошибка записи.
  • void flush() без исключений встречается в некоторых реализациях высшего уровня, например, у PrintWriter метод не объявляет throws IOException. Вместо исключения в таких реализациях состояния ошибок фиксируются внутренне (например, checkError() у PrintWriter).

Аргументы: у метода flush аргументы отсутствуют. Возвращаемое значение: отсутствует (void), при ошибке возможен выброс исключения в реализациях, где оно объявлено.

Когда используется:

  • При желании немедленно передать данные по сети или в файл, не закрывая поток.
  • При реализации протоколов с сигналами конца сообщения (например, отправить заголовки HTTP и начать потоковую передачу).
  • В интерактивных приложениях, чтобы вывести подсказку и заставить буфер системного вывода опустошиться.

Ограничения и тонкости:

  • flush не гарантирует, что данные попали на физический носитель. Для этого требуется вызов низкоуровневых API вроде FileDescriptor.sync() или FileChannel.force(true).
  • частые вызовы flush снижают производительность из-за большого количества системных вызовов.
  • у некоторых реализаций (например, PrintWriter) ошибки записи не бросают исключение, что может приводить к тихим сбоям, если не проверять состояние потока.

Короткие примеры

Пример 1: BufferedWriter и flush:

import java.io.*;

public class Example1 {
    public static void main(String[] args) throws Exception {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("tmp.txt"))) {
            bw.write("Line 1\n");
            bw.flush(); // данные отправлены в FileOutputStream, но не обязательно на диск
        }
    }
}
Результат: файл tmp.txt содержит строку "Line 1".
(Физическая запись на диск может произойти позже, в зависимости от ОС.)

Пример 2: PrintWriter с автофлашем:

import java.io.*;

public class Example2 {
    public static void main(String[] args) {
        PrintWriter pw = new PrintWriter(System.out, true); // autoFlush=true
        pw.println("Hello"); // println вызывает flush при autoFlush=true
    }
}
Результат: в консоль выводится "Hello" без явного вызова flush.

Пример 3: принудительная отправка данных через сокет:

import java.net.*;
import java.io.*;

public class Example3Client {
    public static void main(String[] args) throws Exception {
        try (Socket s = new Socket("localhost", 8888);
             OutputStream os = s.getOutputStream()) {
            os.write("ping".getBytes());
            os.flush(); // заставляет отправить данные TCP-стеку
        }
    }
}
Результат: сервер получит байты "ping" (при корректной настройке сети и серверного кода).

Пример 4: System.out.flush()

public class Example4 {
    public static void main(String[] args) {
        System.out.print("Waiting...");
        System.out.flush(); // делает вывод видимым немедленно
    }
}
Результат: строка "Waiting..." появляется в консоли сразу, даже если буфер вывода не заполнен.

Похожие Java-API и когда выбирать

В Java есть близкие по назначению механизмы:

  • FileChannel.force(boolean) - гарантирует запись данных и, опционально, метаданных на диск; предпочтительнее, когда требуется устойчивость после сбоя питания.
  • FileDescriptor.sync() - аналогичная низкоуровневая операция для обеспечения физической записи на носитель.
  • ServletResponse.flushBuffer() - специфичен для сервлетов; отправляет буфер ответа клиенту (часто используется при chunked transfer).
  • close() - закрытие потока автоматически вызывает сброс буфера в большинстве реализаций; используется при завершении работы с ресурсом.

Выбор:

  • Если требуется немедленная передача данным в другой уровень приложения или в сеть - достаточно flush().
  • Если важна гарантия физического сохранения данных - дополнительно использовать force/ sync.
  • При завершении работы предпочтительнее закрыть ресурс через close() (или try-with-resources), чтобы избежать утечек.

Аналоги в других языках

Краткие отличия и примеры.

Python - метод flush() у файловых объектов; для физической синхронизации используется os.fsync().

# Python
f = open('tmp_py.txt', 'w')
f.write('hello')
f.flush()  # данные сброшены из пользовательского буфера
import os
os.fsync(f.fileno())  # данные физически записаны на диск
f.close()
Результат: в tmp_py.txt содержится "hello"; os.fsync гарантирует запись на диск.

C# (.NET) - у Stream есть Flush() и асинхронный FlushAsync(). У FileStream в новых реализациях есть опция для принудительной записи на диск.

// C#
using (var fs = new FileStream("tmp_cs.txt", FileMode.Create)) {
    byte[] data = System.Text.Encoding.UTF8.GetBytes("hi");
    fs.Write(data, 0, data.Length);
    fs.Flush(); // отправляет буферы, но не всегда принудительно на диск
}
Результат: файл tmp_cs.txt содержит "hi".

Go - у буферизованных писателей bufio.Writer есть Flush(), а у файлового дескриптора File.Sync() для записи на диск.

// Go
w := bufio.NewWriter(file)
w.WriteString("hello")
w.Flush() // сброс в file
file.Sync() // гарантия на диск
Результат: данные доступны в файле; Sync обеспечивает запись на диск.

PHP - функции ob_flush() и flush() управляют буферами вывода; поведение зависит от настройки буферизации и SAPI.

// PHP
echo "chunk 1";
ob_flush();
flush();
// Браузер получит часть ответа немедленно, если сервер и клиент поддерживают это
Результат: при корректной конфигурации браузер видит "chunk 1" сразу.

JavaScript (Node.js) - у потоков есть методы записи; для HTTP-ответов используются res.flushHeaders() или пакеты типа compression. Нативного общего flush() для файлов нет, используется fsync через fs.fsync.

// Node.js (пример жесткой отправки заголовков)
res.writeHead(200, {'Content-Type': 'text/plain'});
res.flushHeaders();
res.write('part1');
Результат: клиент получит заголовки и начальную часть ответа немедленно.

Lua - io.flush() сбрасывает stdout-буфер.

-- Lua
io.write('ok')
io.flush()
Результат: 'ok' выводится немедленно.

Kotlin - использует те же API, что и Java (например, Writer.flush(), OutputStream.flush()).

// Kotlin
val bw = BufferedWriter(FileWriter("tmp_kotlin.txt"))
bw.write("x")
bw.flush()
Результат: файл tmp_kotlin.txt содержит "x".

Отличие основных языков от Java: везде есть разделение между пользовательской буферизацией и физической записью; названия и сигнатуры могут отличаться, но концепция схожа.

Типичные ошибки и примеры

1) Ожидание гарантий записи на диск. Частая ошибка - полагать, что flush() эквивалентно физическому fsync. Для демонстрации:

import java.io.*;

public class Error1 {
    public static void main(String[] args) throws Exception {
        try (FileOutputStream fos = new FileOutputStream("tmp_err.txt")) {
            fos.write("data".getBytes());
            fos.flush(); // не гарантирует запись на диск при сбое питания
        }
    }
}
Результат: данные обычно записаны в файл, но при сбое питания могут потеряться. Для надежности нужен FileDescriptor.sync() или FileChannel.force(true).

2) Вызов flush после close или на закрытом потоке, что приводит к исключению:

import java.io.*;

public class Error2 {
    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bos.close();
        bos.flush(); // многие реализации бросают IOException или IllegalState
    }
}
Результат: в зависимости от реализации может быть выброшено java.io.IOException: Stream closed.

3) Полагание на PrintWriter.flush() для обнаружения ошибок записи. Пример:

import java.io.*;

public class Error3 {
    public static void main(String[] args) throws Exception {
        PrintWriter pw = new PrintWriter(new FileWriter("/invalid/path/file.txt"));
        pw.print("x");
        pw.flush();
        System.out.println(pw.checkError()); // true при ошибке записи, исключения не бросаются
    }
}
Результат: true и никаких исключений при записи; требуется проверять pw.checkError().

4) Избыточные вызовы flush в горячем цикле приводят к падению производительности. Для снижения накладных расходов стоит буферизовать и вызывать flush реже.

Изменения в API

За последние версий Java собственный контракт flush() у интерфейса Flushable и основных реализаций не претерпел значительных изменений. Несколько примечательных моментов:

  • Flushable добавлен в Java 1.5 как унифицированный интерфейс для операций сброса буферов.
  • Поведение PrintWriter, которое не выбрасывает IOException, сохраняется и осталось источником потенциальных ошибок - это проверяемое поведение исторически и не менялось.
  • Для гарантий на запись на диск используются независимые API (FileChannel.force, FileDescriptor.sync), которые эволюционировали независимо от flush.
  • В современных версиях Java рекомендуется использовать try-with-resources и асинхронные API там, где это уместно; сам flush() остался синхронным методом.

Расширенные примеры и редкие сценарии

Пример A: flush + FileChannel.force - гарантия устойчивости:

Пример java
import java.io.*;
import java.nio.channels.FileChannel;

public class Adv1 {
    public static void main(String[] args) throws Exception {
        try (FileOutputStream fos = new FileOutputStream("durable.txt");
             FileChannel ch = fos.getChannel()) {
            fos.write("important".getBytes());
            fos.flush();
            ch.force(true); // гарантирует запись данных и метаданных на диск
        }
    }
}
Результат: durable.txt содержит "important"; ch.force(true) повышает вероятность сохранения при сбое питания.

Пример B: взаимодействие буферизации и GZIPOutputStream - необходимость finish/close:

Пример java
import java.io.*;
import java.util.zip.GZIPOutputStream;

public class Adv2 {
    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (GZIPOutputStream gz = new GZIPOutputStream(baos);
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(gz))) {
            bw.write("text");
            bw.flush(); // очищает BufferedWriter, но GZIPOutputStream хранит метаданные
            // Для корректного завершения gzip-структуры требуется gz.finish() или close()
        }
        byte[] result = baos.toByteArray();
        System.out.println(result.length);
    }
}
Результат: длина массива больше нуля; при отсутствии finish/close результат может быть некорректен для распаковки.

Пример C: протоколы поверх TCP - flush + socket.shutdownOutput()

Пример java
// клиент
import java.net.*;
import java.io.*;

public class Adv3Client {
    public static void main(String[] args) throws Exception {
        try (Socket s = new Socket("localhost", 9999);
             OutputStream os = s.getOutputStream()) {
            os.write("message".getBytes());
            os.flush();
            s.shutdownOutput(); // сигнал, что данные закончились
        }
    }
}
Результат: сервер получает данные и EOF на входном потоке, что удобно для простых протоколов запроса-ответа.

Пример D: многопоточная запись и видимость данных - один поток пишет и flush, другой читает из те же ресурсов (например, файл). При совместном доступе рекомендуется синхронизация и явные механизмы согласованности.

Пример java
// Схема: producer пишет и flush, consumer периодически читает содержимое файла
// В реальном коде нужно учитывать проблемы кэширования ОС и блокировки файлов
Результат: данные становятся видимы читателю после flush, но возможна задержка из-за кэшей ОС; для консистентности применяются дополнительные механизмы.

Пример E: использование flush в сервлетах для chunked transfer:

Пример java
// в методе doGet(HttpServletRequest req, HttpServletResponse resp)
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
out.print("part1\n");
out.flush(); // отправляет часть ответа клиенту
// выполняется долгий процесс
out.print("part2\n");
Результат: клиент получает "part1" сразу и может начать обработку данных до завершения сервера.

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

Пример java
// Общая рекомендация: при высоких нагрузках использовать неблокирующие каналы/асинхронные API вместо частого flush в синхронном коде
Результат: повышение пропускной способности и меньшая задержка при корректной архитектуре.

джава flush function comments

En
Flush Flushes the stream