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

Справочная статья по BufferedOutputStream для Java
Раздел: Потоки ввода-вывода (Streams) байтовые
BufferedOutputStream(OutputStream out)

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

Класс java.io.BufferedOutputStream предоставляет буферизированную обёртку вокруг любого потока вывода типа OutputStream. Цель - уменьшить число системных вызовов при множественных операциях записи путём накопления данных в памяти и единоразовой записи большого блока в базовый поток.

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

Конструкторы и основные методы

  • BufferedOutputStream(OutputStream out)
    • Аргументы: out - базовый поток для записи. Не возвращает значение.
    • Поведение: создаёт буфер по умолчанию (обычно 8192 байт).
  • BufferedOutputStream(OutputStream out, int size)
    • Аргументы: out - базовый поток; size - размер буфера в байтах.
    • Возвращаемое значение: конструктор - объект класса.
    • Особенности: если size <= 0, выбрасывается IllegalArgumentException.
  • void write(int b)
    • Аргументы: байт для записи (только младшие 8 бит используются).
    • Поведение: помещает байт в внутренний буфер; при переполнении буфер сбрасывается в базовый поток.
    • Исключения: может выбросить IOException при ошибках базового потока.
  • void write(byte[] b, int off, int len)
    • Аргументы: массив байт, смещение и длина записи.
    • Поведение: копирует указанный диапазон в буфер; если длина превышает размер буфера, данные могут быть записаны напрямую в базовый поток без промежуточного копирования.
    • Исключения: IOException, IndexOutOfBoundsException при неверных off/len, NullPointerException при null-массиве.
  • void flush()
    • Аргументы: нет.
    • Поведение: немедленно сбрасывает содержимое буфера в базовый поток и вызывает соответствующие операции у него; полезно для гарантированной доставки данных (например, в сеть).
    • Исключения: IOException.
  • void close()
    • Аргументы: нет.
    • Поведение: сначала выполняется flush(), затем закрывается базовый поток. После закрытия дальнейшие вызовы записывающих методов приведут к IOException.
    • Исключения: IOException.

Возвращаемых значений у методов записи нет (void). Исключения главная сигнальная механика при ошибках.

Ограничения и свойства

  • Буфер не синхронизирован; при конкурентном доступе требуется внешняя синхронизация.
  • Не подходит для символьной записи без преобразования байтов (использовать OutputStreamWriter или BufferedWriter для текста).

Короткие примеры использования

Пример 1. Запись массива байт в файл (по умолчанию буфер)

import java.io.*;

public class Example1 {
    public static void main(String[] args) throws IOException {
        byte[] data = "Привет, BufferedOutputStream!".getBytes("UTF-8");
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("out1.bin"))) {
            bos.write(data);
        }
        // Файл out1.bin создан
    }
}
Результат: файл out1.bin со строкой в кодировке UTF-8; программа завершается без вывода в консоль.

Пример 2. Указание собственного размера буфера и запись по одному байту

import java.io.*;

public class Example2 {
    public static void main(String[] args) throws IOException {
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("out2.bin"), 1024)) {
            for (int i = 0; i < 100; i++) bos.write(i); // записи собираются в буфер
            bos.flush(); // опционально, т.к. close() также выполнит flush
        }
    }
}
Результат: out2.bin содержит первые 100 байтов (0..99).

Пример 3. Буферизированная запись в сетевой сокет

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

public class Example3Client {
    public static void main(String[] args) throws Exception {
        try (Socket s = new Socket("example.com", 80);
             BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream())) {
            String req = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
            bos.write(req.getBytes("UTF-8"));
            bos.flush();
            // далее чтение ответа через InputStream
        }
    }
}
Результат: сформированный HTTP-запрос отправлен в один или несколько системных вызовов, эффективность выше, чем при построчной отправке без буфера.

Похожие инструменты в Java

  • BufferedWriter - буфер для символьного вывода. Предпочтителен при работе с текстом и указании кодировки через OutputStreamWriter.
  • FileOutputStream - низкоуровневый поток, лучше использовать вместе с буфером для производительности.
  • DataOutputStream - добавляет методы для записи примитивов (int, long и т. п.). Часто комбинируется с буфером: new DataOutputStream(new BufferedOutputStream(...)).
  • java.nio.channels.FileChannel и ByteBuffer - альтернативный подход через NIO; уместен при необходимости прямого управления буферами и более тонкой производительности.

Выбор зависит от задачи: для текстовой записи - BufferedWriter; для примитивов и структурированных данных - DataOutputStream + BufferedOutputStream; для высокопроизводительных и неблокирующих операций - NIO.

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

  • Python: io.BufferedWriter. Оборачивает файловый объект, имеет методы write, flush, close. Пример:
    import io
    with open('py_out.bin','wb') as f:
        with io.BufferedWriter(f, buffer_size=8192) as bw:
            bw.write(b'Hello from Python')
    
    Результат: файл py_out.bin с указанными байтами.
  • Node.js (JavaScript): fs.createWriteStream(path, { highWaterMark: 8192 }). Поток пишет с контролем буферного порога; метод write возвращает булево значение, сигнализирующее о заполнении внутреннего буфера.
    const fs = require('fs');
    const ws = fs.createWriteStream('node_out.bin', { highWaterMark: 8192 });
    ws.write(Buffer.from('Hello from Node'));
    ws.end();
    
    Результат: node_out.bin создан.
  • C#: System.IO.BufferedStream, конструктор принимает базовый Stream и размер буфера. Поведение и API очень похожи на Java.
    using(var fs = File.Create("csharp_out.bin"))
    using(var bs = new BufferedStream(fs, 8192)) {
        byte[] b = System.Text.Encoding.UTF8.GetBytes("Hello C#");
        bs.Write(b,0,b.Length);
    }
    
    Результат: csharp_out.bin с данными.
  • Go: bufio.NewWriter. Возвращает писатель с методом Flush().
    package main
    import (
      "bufio"
      "os"
    )
    func main(){
      f,_:=os.Create("go_out.bin")
      w:=bufio.NewWriterSize(f,8192)
      w.Write([]byte("Hello Go"))
      w.Flush()
      f.Close()
    }
    
    Результат: go_out.bin с данными.
  • PHP: потоки PHP имеют собственную буферизацию, но можно управлять через stream_set_write_buffer($stream, $buffer).
    $f = fopen('php_out.bin','wb');
    stream_set_write_buffer($f, 8192);
    fwrite($f, "Hello PHP");
    fclose($f);
    
    Результат: php_out.bin создан.
  • Lua: стандартный io имеет внутренную буферизацию; явного BufferedOutputStream нет; можно использовать file:write и file:flush().
  • Kotlin: использует Java-реализацию: BufferedOutputStream из JDK; синтаксис Kotlin компактнее.

Отличия: во многих языках концепция аналогична - обёртка вокруг базового потока с внутренним буфером. Node.js дополнительно возвращает сигнал о заполнении буфера через результат write. Python и Go дают явный flush, как и Java.

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

Ошибка 1. Забыт flush/close - потеря данных

import java.io.*;

public class Error1 {
    public static void main(String[] args) throws Exception {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("err1.bin"));
        bos.write("data".getBytes());
        // bos.close() забыли
    }
}
Результат: файл err1.bin может быть пустым или неполным, данные остались в буфере.

Ошибка 2. Переиспользование закрытого потока

try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("err2.bin"))) {
    bos.write(1);
}
// далее
// bos.write(2); // компилятор не даст, но если попытаться через ссылку - IOException
Результат: при записи в закрытый поток будет выброшено IOException.

Ошибка 3. Неверные off/len при использовании write(byte[], off, len)

byte[] data = new byte[10];
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("err3.bin"));
bos.write(data, 5, 10); // IndexOutOfBoundsException
Результат: вылет исключения IndexOutOfBoundsException до записи.

Ошибка 4. Ожидание возвращаемого количества записанных байт

// Неверно ожидать возвращаемое значение
bos.write(data); // возвращает void, нет числа записанных байт
Результат: неправильная логика, код не получает подтверждение от write о числе записанных байт, требуется внешняя проверка/исключения.

Изменения за последние версии Java

Класс BufferedOutputStream является частью пакета java.io с ранних версий JDK и не претерпевал значительных изменений в последних релизах. Основные аспекты остаются прежними: конструкторы, поведение буферизации, методы write, flush, close.

В современных версиях Java улучшения в общем случае коснулись производительности подсистем ввода-вывода и реализации низкоуровневых потоков, но публичный API BufferedOutputStream не изменялся и не помечен как устаревший.

Расширенные и редкие варианты применения

Пример 1. Комбинация с GZIPOutputStream для компрессии с буферизацией

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

public class Adv1 {
    public static void main(String[] args) throws Exception {
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("data.gz"));
             GZIPOutputStream gzos = new GZIPOutputStream(bos)) {
            gzos.write("Большой текст для сжатия".getBytes("UTF-8"));
            // GZIPOutputStream.flush() вызовет запись в bos при необходимости
        }
    }
}
Результат: data.gz - сжатый файл. Буферизация перед gzip уменьшает число операций записи в файл.

Пример 2. Подсчёт записанных байт через наследование

Пример java
import java.io.*;

class CountingBufferedOutputStream extends BufferedOutputStream {
    private long count = 0;
    public CountingBufferedOutputStream(OutputStream out, int size) { super(out, size); }
    @Override
    public synchronized void write(int b) throws IOException {
        super.write(b);
        count++;
    }
    @Override
    public synchronized void write(byte[] b, int off, int len) throws IOException {
        super.write(b, off, len);
        count += len;
    }
    public long getCount() { return count; }
}

public class Adv2 {
    public static void main(String[] args) throws Exception {
        try (CountingBufferedOutputStream cbos = new CountingBufferedOutputStream(new FileOutputStream("count.bin"), 4096)) {
            cbos.write(new byte[1000]);
            System.out.println(cbos.getCount());
        }
    }
}
Результат: в консоль выведется число 1000; файл count.bin содержит 1000 нулевых байтов.

Пример 3. Буферизация для сетевого протокола с принудительным сбросом по окончании сообщения

Пример java
// Сценарий: отправлять сообщения, заканчивая '
', и немедленно отправлять их в сеть
try (Socket s = new Socket(host, port);
     BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream())) {
    String msg = "EVENT:123\n";
    bos.write(msg.getBytes("UTF-8"));
    bos.flush(); // гарантирует доставку события
}
Результат: каждое сообщение приходит на сервер без долгого ожидания; flush используется для границ сообщений.

Пример 4. Сравнение производительности: с буфером и без

Пример java
// Укорочённый пример измерения
long t1 = System.currentTimeMillis();
try (OutputStream os = new FileOutputStream("plain.bin")) {
    for (int i=0;i<1_000_000;i++) os.write(i);
}
long t2 = System.currentTimeMillis();
try (OutputStream os = new BufferedOutputStream(new FileOutputStream("buf.bin"))) {
    for (int i=0;i<1_000_000;i++) os.write(i);
}
long t3 = System.currentTimeMillis();
System.out.println("plain: " + (t2-t1) + " ms, buffered: " + (t3-t2) + " ms");
Результат: plain обычно значительно медленнее, buffered даёт заметный выигрыш; точные числа зависят от системы.

Пояснения

  • Комбинация с компрессией и подсчётом полезна для логирования, резервного копирования и сетевой передачи больших объёмов.
  • При использовании в многопоточных приложениях требуется внешняя синхронизация, либо использовать отдельные потоки/синхронизированные блоки.
  • Выбор размера буфера зависит от характера операций: слишком маленький - высокая нагрузка; слишком большой - ненужное потребление памяти.

джава BufferedOutputStream function comments

En
BufferedOutputStream Adds buffering to an output stream, improving performance