Flush: примеры (JAVA)
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 - гарантия устойчивости:
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:
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()
// клиент
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, другой читает из те же ресурсов (например, файл). При совместном доступе рекомендуется синхронизация и явные механизмы согласованности.
// Схема: producer пишет и flush, consumer периодически читает содержимое файла
// В реальном коде нужно учитывать проблемы кэширования ОС и блокировки файлов
Результат: данные становятся видимы читателю после flush, но возможна задержка из-за кэшей ОС; для консистентности применяются дополнительные механизмы.
Пример E: использование flush в сервлетах для chunked transfer:
// в методе 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 остается синхронной операцией и может требовать дополнительных асинхронных механизмов для высоконагруженных систем.
// Общая рекомендация: при высоких нагрузках использовать неблокирующие каналы/асинхронные API вместо частого flush в синхронном коде
Результат: повышение пропускной способности и меньшая задержка при корректной архитектуре.