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

Обзор BufferedWriter и практическое применение
Раздел: Ввод-вывод (I/O) с буферизацией
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) с буферизацией и кодировкой

Пример java
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 и опций

Пример java
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 или сторонние логгеры).

Пример java
// Пример синхронизации
synchronized (sharedBufferedWriter) {
    sharedBufferedWriter.write("thread output\n");
}
(гарантирует последовательность записей без перемешивания байтов)

4. Буферизированная запись в память через StringWriter для тестов

Пример java
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). Это снижает пиковое потребление оперативной памяти и улучшает пропускную способность.

Пример java
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 для записи в файл.

Пример java
// Псевдокод: producer добавляет строки в очередь; consumer читает и пишет через BufferedWriter
(подход уменьшает задержки в производительных потоках, но требует управления очередью и жизненным циклом)

джава BufferedWriter function comments

En
BufferedWriter Writes text to a character-output stream, buffering characters