BufferedWriter: примеры (JAVA)
BufferedWriter(Writer out)Описание BufferedWriter
Класс java.io.BufferedWriter служит для буферизированной записи символов в поток вывода. Основная цель использования - уменьшение числа обращений к базовому устройству ввода/вывода за счет накопления данных в памяти и записи блоками. BufferedWriter работает поверх любого объекта, реализующего абстрактный класс Writer, например FileWriter, OutputStreamWriter или StringWriter.
Конструкторы:
- BufferedWriter(Writer out) - создаёт буфер по умолчанию (обычно 8192 символа) вокруг указанного Writer. Параметр:
out- обязательный объект типа Writer. Исключения: если out равен null, будет выброшено NullPointerException при первом обращении. - BufferedWriter(Writer out, int sz) - создаёт буфер размером
szсимволов. Параметры:out- Writer;sz- размер буфера в символах. Еслиszменьше 1, генерируется IllegalArgumentException.
Основные методы и их поведение:
- write(int c) - записывает один символ. Возвращаемого значения нет. Может бросать IOException.
- write(char[] cbuf, int off, int len) - записывает фрагмент массива символов; параметры: массив, смещение, длина. Проверяется корректность границ; при нарушении выбрасывается IndexOutOfBoundsException. При ошибках ввода-вывода - IOException.
- write(String s, int off, int len) - записывает часть строки; аналогично с массивом.
- newLine() - записывает символ(ы) новой строки, для платформы используется System.lineSeparator(). Нет возвращаемого значения; может бросать IOException.
- flush() - сбрасывает буфер в базовый Writer, но не закрывает поток. Может бросать IOException.
- close() - сначала выполняет flush(), затем закрывает вложенный Writer. После close дальнейшие операции приведут к IOException.
- append(CharSequence csq), append(CharSequence csq, int start, int end), append(char c) - методы интерфейса Appendable; возвращают текущий Writer (this). Могут бросать IOException.
Возвращаемые значения: большинство методов записи не возвращают значений; append возвращает ссылку на Writer для цепочек вызовов. Исключения: IOException для ошибок ввода‑вывода; IllegalArgumentException для некорректного размера буфера; IndexOutOfBoundsException для неверных границ при записи массивов/строк.
Когда используется:
- При записи больших объёмов текстовых данных в файл или сетевой поток для повышения производительности.
- Когда требуется контролируемая запись строк с разделителями платформы через newLine().
- В сочетании с OutputStreamWriter для задания кодировки символов.
Короткие примеры использования
Пример 1: базовая запись в файл через FileWriter с try-with-resources.
import java.io.*;
public class Example1 {
public static void main(String[] args) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"))) {
bw.write("Первая строка");
bw.newLine();
bw.write("Вторая строка");
}
}
}
(в файле out.txt будет) Первая строка Вторая строка
Пример 2: указание кодировки через OutputStreamWriter.
import java.io.*;
import java.nio.charset.StandardCharsets;
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream("utf8.txt"), StandardCharsets.UTF_8))) {
bw.write("Русский текст и emoji ????");
}
(файл utf8.txt записан в кодировке UTF-8, читается корректно)
Пример 3: собственный размер буфера.
import java.io.*;
char[] data = new char[20000];
for (int i = 0; i < data.length; i++) data[i] = 'x';
try (BufferedWriter bw = new BufferedWriter(new FileWriter("big.txt"), 16384)) {
bw.write(data);
}
(файл big.txt содержит 20000 символов 'x')
Пример 4: использование Files.newBufferedWriter (NIO).
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
try (BufferedWriter bw = Files.newBufferedWriter(Paths.get("nio.txt"), StandardCharsets.UTF_8)) {
bw.write("NIO writer example");
}
(файл nio.txt создан и содержит строку)
Аналоги в Java и их особенности
Несколько похожих инструментов в Java и сценарии предпочтения:
- PrintWriter - удобен при форматированном выводе и наличии методов println/printf. Имеет конструктор с автоочисткой ошибок; может не бросать IOException напрямую (методы checkError).
- BufferedOutputStream - работает с байтовыми потоками (OutputStream). Предпочтителен при записи бинарных данных. Для текстовой записи требуется обёртка OutputStreamWriter.
- Files.newBufferedWriter (java.nio.file) - современный способ создания BufferedWriter с указанием кодировки и опций открытия файла. Удобнее для NIO-ориентированных приложений.
- StringWriter - не пишет в файл, но полезен, когда требуется буфер в памяти (например, для тестов или сборки строки).
Рекомендации:
- Для текстовой записи с форматированием и println - предпочтение PrintWriter.
- Для бинарных данных - BufferedOutputStream поверх FileOutputStream.
- Для указания кодировки и гибких опций файла - Files.newBufferedWriter.
Аналоги в других языках и отличия
Краткое сравнение с реализациями в популярных языках и примеры.
Python
Модуль io предоставляет io.BufferedWriter (байтовая) и текстовый интерфейс через open(..., buffering=) или io.TextIOWrapper над BufferedWriter.
# Python
import io
with open('py.txt', 'w', buffering=8192, encoding='utf-8') as f:
f.write('Привет\n')
(файл py.txt содержит строку в UTF-8)
JavaScript (Node.js)
Используется fs.createWriteStream, который буферизует данные в памяти и записывает асинхронно.
// Node.js
const fs = require('fs');
const stream = fs.createWriteStream('node.txt', { encoding: 'utf8' });
stream.write('Строка\n');
stream.end();
(файл node.txt создан)
PHP
Функции fopen/fwrite и file_put_contents. file_put_contents удобен для простых задач; fopen/fwrite даёт контроль над буферизацией и режимом.
// PHP
file_put_contents('php.txt', "Текст\n");
(php.txt содержит строку)
C#
System.IO.StreamWriter обеспечивает буферизацию символов; есть также BufferedStream для байтов.
// C#
using (var sw = new StreamWriter("cs.txt", false, System.Text.Encoding.UTF8))
{
sw.WriteLine("Привет из C#");
}
(cs.txt содержит строку в UTF-8)
Go
Пакет bufio содержит bufio.NewWriter, метод Flush обязателен для записи буфера.
// Go
package main
import (
"bufio"
"os"
)
func main() {
f, _ := os.Create("go.txt")
w := bufio.NewWriterSize(f, 4096)
w.WriteString("Go text\n")
w.Flush()
f.Close()
}
(go.txt содержит строку)
Lua
IO в Lua через io.open и file:write - буферизация управляется реализацией, явного BufferedWriter нет.
-- Lua
local f = io.open('lua.txt', 'w')
f:write('Lua line\n')
f:close()
(lua.txt содержит строку)
Отличия от Java: в большинстве языков есть аналог буферизации, но особенности API, способ задания кодировки и поведение при ошибках различаются. В Java явное разделение символов и байтов через Writer/OutputStream даёт более явный контроль над преобразованием кодировок.
Типичные ошибки и примеры
Ниже перечислены распространённые ошибки при работе с BufferedWriter и демонстрационные примеры.
1. Забытая flush/close - потеря данных
import java.io.*;
public class BadExample {
public static void main(String[] args) throws Exception {
BufferedWriter bw = new BufferedWriter(new FileWriter("lost.txt"));
bw.write("Данные");
// нет bw.close() и нет flush()
}
}
(в некоторых средах файл может остаться пустым, т.к. буфер не сброшен)
2. Повторная запись в закрытый поток
try (BufferedWriter bw = new BufferedWriter(new FileWriter("f.txt"))) {
bw.write("ok");
}
// далее
BufferedWriter bw2 = new BufferedWriter(new FileWriter("f.txt"));
bw2.close();
try {
bw2.write("will fail");
} catch (IOException e) {
System.out.println("Ошибка: поток закрыт");
}
Ошибка: поток закрыт
3. Неправильное использование для бинарных данных
// Неправильно: попытка записать байты через BufferedWriter
byte[] blob = {0, 1, 2};
// нет прямого метода записи байтов - требуется OutputStream
(лучшее решение: использовать BufferedOutputStream для байтов)
4. Неправильная кодировка (использование платформенной по умолчанию)
try (BufferedWriter bw = new BufferedWriter(new FileWriter("enc.txt"))) {
bw.write("Текст с юникодом - ✓");
}
(на некоторых системах кодировка по умолчанию не UTF-8, могут появиться искажённые символы; стоит указывать Charset явно)
5. Малый размер буфера при интенсивной записи
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"), 1); // плохой выбор
(частые обращения к базовому Writer, потеря преимуществ буферизации)
Изменения в API и заметные улучшения
API BufferedWriter как часть java.io остаётся стабильным долгое время. Ключевые изменения, влияющие на использование:
- Java 7: появление try-with-resources обеспечивает безопасное закрытие потоков, что уменьшило число утечек ресурсов.
- Java 7/8/9: развитие API NIO (java.nio.file.Files) добавило удобные фабрики, такие как Files.newBufferedWriter, позволяющие указать Charset и опции доступа при создании writer.
- Java 9 модульная система не изменила BufferedWriter, но повлияла на упаковку библиотек и доступность классов в модульных приложениях.
Прямых изменений в сигнатуре BufferedWriter в последних релизах не наблюдалось; рекомендации касаются более безопасного управления ресурсами и использования NIO-утилит.
Расширенные и нетипичные примеры
1. Запись сжатого текстового файла (GZIP) с буферизацией и кодировкой
import java.io.*;
import java.util.zip.GZIPOutputStream;
import java.nio.charset.StandardCharsets;
public class GzipWrite {
public static void main(String[] args) throws IOException {
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new GZIPOutputStream(new FileOutputStream("out.gz")),
StandardCharsets.UTF_8),
16384)) {
bw.write("Большой текст, который будет сжат\n");
}
}
}
(файл out.gz содержит сжатую UTF-8 последовательность; открыть через gunzip/архиватор)
2. Дописывание в файл с помощью Files.newBufferedWriter и опций
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.nio.file.StandardOpenOption;
Files.write(Paths.get("append.txt"), "Первая\n".getBytes(StandardCharsets.UTF_8));
try (BufferedWriter bw = Files.newBufferedWriter(Paths.get("append.txt"),
StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
bw.write("Допись\n");
}
(файл append.txt будет содержать две строки)
3. Потокобезопасность: какие ограничения и обходы
BufferedWriter не синхронизирован. При конкурентном доступе рекомендуется синхронизировать общий объект вручную или использовать отдельные потоки/логгер с поддержкой конкурентности (например, java.util.logging с FileHandler или сторонние логгеры).
// Пример синхронизации
synchronized (sharedBufferedWriter) {
sharedBufferedWriter.write("thread output\n");
}
(гарантирует последовательность записей без перемешивания байтов)
4. Буферизированная запись в память через StringWriter для тестов
import java.io.*;
StringWriter sw = new StringWriter();
try (BufferedWriter bw = new BufferedWriter(sw)) {
bw.write("Тестовая строка");
}
String result = sw.toString();
System.out.println(result);
Тестовая строка
5. Частичная запись больших объектов: управление памятью
При записи очень больших данных рекомендуется разбивать запись на части и выбирать разумный размер буфера (например, 8–64 KiB). Это снижает пиковое потребление оперативной памяти и улучшает пропускную способность.
char[] chunk = new char[32*1024];
// заполнять и писать по частям
try (BufferedWriter bw = new BufferedWriter(new FileWriter("huge.txt"), 32*1024)) {
for (int i = 0; i < 1000; i++) {
// заполнить chunk
bw.write(chunk);
}
}
(файл huge.txt создаётся по частям без удержания всего содержимого в памяти)
6. Комбинация с асинхронной записью (пример идеи)
BufferedWriter сам по себе блокирующий. При требовании неблокирующей записи можно помещать данные в очередь (BlockingQueue) и отдавать отдельному поток-демону, который использует BufferedWriter для записи в файл.
// Псевдокод: producer добавляет строки в очередь; consumer читает и пишет через BufferedWriter
(подход уменьшает задержки в производительных потоках, но требует управления очередью и жизненным циклом)