BufferedInputStream: примеры (JAVA)
BufferedInputStream(InputStream in)Описание и сигнатуры
Класс BufferedInputStream из пакета java.io применяется для добавления буферизации к любому InputStream. Буферизация повышает производительность при множественных мелких операциях чтения и позволяет использовать возможности, такие как mark и reset, без непосредственного обращения к более медленному источнику данных.
Основные конструкторы:
BufferedInputStream(InputStream in)- создаёт буфер по умолчанию (обычно 8192 байт) вокруг указанного входного потока.BufferedInputStream(InputStream in, int size)- создаёт буфер указанного размера. Еслиsize <= 0, будет выброшеноIllegalArgumentException.
Главные методы и их поведение:
int read()- читает один байт и возвращает значение в диапазоне 0–255; при достижении конца возвращает-1. Может выброситьIOException.int read(byte[] b)- пытается прочитать доb.lengthбайт; возвращает количество реально прочитанных байт или-1при EOF. Еслиb.length == 0, возвращает 0.int read(byte[] b, int off, int len)- читает доlenбайт в массив с указанного смещения; возвращает количество прочитанных байт или-1при EOF. Не гарантируется чтение ровноlenбайт за один вызов.long skip(long n)- пропускает доnбайт, возвращает фактическое число пропущенных. Может возвращать 0, если EOF или если поток не поддерживает пропуск.int available()- возвращает оценку количества байт, доступных для немедленного чтения без блокирования. Значение может быть 0 и не гарантируется точность для всех типов потоков.void close()- закрывает сам поток и оборачиваемый поток, освобождая ресурсы.void mark(int readlimit)- устанавливает метку, позволяющую вернуться методомreset()пока не будет прочитано болееreadlimitбайт после метки. Для BufferedInputStream поддержка метки реализована и управляется размером внутреннего буфера: если после метки было прочитано более размера буфера, метка может стать недействительной.void reset()- возвращает позицию чтения к последней метке; если метка недействительна, бросаетIOException.boolean markSupported()- возвращаетtrue(в BufferedInputStream поддержка метки доступна).
Особенности использования:
- Буферизация полезна при чтении с диска, сети или других медленных источников.
- Размер буфера выбирается в зависимости от профиля доступа: для последовательного чтения можно использовать небольшой буфер; для множества мелких операций - увеличить размер для снижения числа системных вызовов.
- Метод
available()даёт приблизительную оценку и не предназначен для определения конца потока.
Короткие примеры использования
Пример 1. Чтение всех байт из потока (ByteArrayInputStream используется для демонстрации):
import java.io.*;
public class Example1 {
public static void main(String[] args) throws Exception {
byte[] data = "Привет".getBytes("UTF-8");
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data))) {
int b;
while ((b = bis.read()) != -1) {
System.out.print((char) b);
}
}
}
}
Результат: Привет
Пример 2. Чтение в массив с обработкой частичного чтения:
import java.io.*;
public class Example2 {
public static void main(String[] args) throws Exception {
byte[] data = "Hello BufferedInputStream".getBytes("UTF-8");
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data), 8)) {
byte[] buf = new byte[10];
int read = bis.read(buf); // может прочитать менее 10 байт
System.out.println("Прочитано: " + read);
System.out.println(new String(buf, 0, read, "UTF-8"));
}
}
}
Результат: Прочитано: 10 Hello Buff
Пример 3. mark/reset внутри размера буфера:
import java.io.*;
public class Example3 {
public static void main(String[] args) throws Exception {
byte[] data = "ABCDE".getBytes("UTF-8");
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data), 4)) {
System.out.print((char) bis.read()); // A
bis.mark(4);
System.out.print((char) bis.read()); // B
System.out.print((char) bis.read()); // C
bis.reset();
System.out.print((char) bis.read()); // B again
}
}
}
Результат: ABC B (в потоке выводится: AB C B без пробелов, здесь для наглядности показаны шаги)
Похожие классы в Java и их особенности
- BufferedReader - аналог для символьных потоков (
Reader). Предпочтителен при работе с текстом и кодировками, предоставляет методreadLine(). - ByteArrayInputStream - поток поверх массива байт, быстрый для тестов и небольших данных, не использует дополнительной буферизации.
- PushbackInputStream - позволяет «вернуть» прочитанные байты обратно в поток; пригоден для простого парсинга при необходимости отката чтения.
- FileInputStream - базовый поток чтения из файла. Часто оборачивается в BufferedInputStream для повышения скорости.
- Channels (NIO) и java.nio.file.Files.newInputStream - альтернативы на базе NIO, дают более гибкий контрол над операциями ввода-вывода и могут лучше масштабироваться в многопоточных сценариях.
Выбор: для побайтного или поблочного чтения бинарных данных удобен BufferedInputStream. Для текста с кодировками лучше BufferedReader. Для высокопроизводительных серверов и неблокирующих операций стоит рассмотреть NIO-решения.
Аналоги в других языках
- Python (io.BufferedReader)
import io buf = io.BufferedReader(io.BytesIO(b"Hello")) print(buf.read())Результат: b'Hello'
Отличие: Python явно разделяет двоичные и текстовые потоки, API удобен для чтения и доступен в стандартной библиотеке. - C# (System.IO.BufferedStream)
using System; using System.IO; class P { static void Main(){ byte[] data = System.Text.Encoding.UTF8.GetBytes("Hi"); using(var ms = new MemoryStream(data)) using(var bs = new BufferedStream(ms)){ int b = bs.ReadByte(); Console.WriteLine((char)b); } }}Результат: H
Отличие: BufferedStream служит оболочкой и в целом схож с BufferedInputStream. - Go (bufio.Reader)
package main import ( "bufio" "bytes" "fmt" ) func main(){ r := bufio.NewReader(bytes.NewBufferString("Hello")) s, _ := r.ReadString('o') fmt.Println(s) }Результат: Hello
Отличие: встроенная модель чтения с удобными методами (ReadString, Peek) и эффективной реализацией буфера. - JavaScript (Node.js, stream + Buffer)
const fs = require('fs'); const rs = fs.createReadStream('file.bin', { highWaterMark: 8192 }); rs.on('data', chunk => console.log('chunk.length=', chunk.length));Результат: chunk.length= 8192 ...
Отличие: в Node.js поток сам буферизует данные, highWaterMark управляет размером буфера. - PHP
$fp = fopen('php://memory', 'r+'); fwrite($fp, "data"); rewind($fp); echo stream_get_contents($fp);Результат: data
Отличие: PHP использует стандартные C-стайловые буферы и функции для чтения; явной оболочки для InputStream как в Java нет. - Kotlin - использует те же Java-классы (
BufferedInputStream) с синтаксическим сокращением; поведение идентичное. - Lua - стандартная библиотека предоставляет file:read, где буферизация обрабатывается библиотекой С, явной оболочки нет.
Типичные ошибки и демонстрации
1) Ожидание, что read(byte[]) всегда заполнит массив полностью:
// Неправильное предположение
byte[] buf = new byte[1024];
int n = bis.read(buf);
// если нужно точно n байт, нужно вызывать в цикле
Проблема: Метод может вернуть меньше байт, чем размер массива, особенно при чтении с сетевого потока или в конце данных.
2) Неправильное использование mark/reset за пределами буфера:
byte[] data = "1234567890".getBytes();
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data), 4)){
bis.mark(4);
bis.read(new byte[6]); // прочитано больше, чем размер буфера
bis.reset(); // IOException - метка недействительна
}
Результат: java.io.IOException: Resetting to invalid mark
3) Передача неположительного размера буфера:
new BufferedInputStream(new FileInputStream("file"), 0);
Результат: java.lang.IllegalArgumentException: Buffer size <= 0
4) Чтение после закрытия потока:
BufferedInputStream bis = new BufferedInputStream(...);
bis.close();
bis.read();
Результат: java.io.IOException: Stream closed
5) Зависимость от available() как от точного размера оставшихся данных:
int avail = bis.available();
byte[] b = new byte[avail];
bis.read(b); // может не прочитать все данные, доступные позже
Проблема: Для сетевых или потоковых источников available() даёт только оценку и не заменяет корректного цикла чтения.
Изменения в реализации и версиях JDK
Класс BufferedInputStream стабилен и не претерпевал значительных API-изменений в недавних релизах. Основные моменты:
- Внутренние оптимизации в реализации JDK могли меняться между версиями для улучшения производительности и безопасности, но публичный API остался прежним.
- Развитие платформы сместило акцент в сторону NIO и асинхронных API для высоконагруженных приложений; тем не менее BufferedInputStream остаётся валидным решением для синхронного чтения.
Для точных изменений между конкретными версиями можно свериться с заметками к релизам JDK и исходниками в OpenJDK.
Расширенные и нетипичные сценарии
1) Комбинация с CipherInputStream для чтения зашифрованных данных:
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
// Псевдокод: читает зашифрованный файл через буфер
byte[] key = new byte[16]; // ключ для примера
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
try (InputStream fis = new FileInputStream("enc.bin");
BufferedInputStream bis = new BufferedInputStream(fis);
CipherInputStream cis = new CipherInputStream(bis, cipher)) {
byte[] buf = new byte[4096];
int r;
while ((r = cis.read(buf)) != -1) {
// обработка расшифрованных данных
}
}
Результат: (в процессе выполнения читаются и расшифровываются байты из файла)
2) Использование с GZIPInputStream для распаковки большого файла по частям:
import java.util.zip.GZIPInputStream;
// Чтение и распаковка
try (InputStream fis = new FileInputStream("file.gz");
BufferedInputStream bis = new BufferedInputStream(fis, 16384);
GZIPInputStream gis = new GZIPInputStream(bis)) {
byte[] buf = new byte[8192];
int n;
while ((n = gis.read(buf)) != -1) {
// запись в целевой поток
}
}
Результат: Файл распакован и прочитан блоками по 8192 байта
3) Эффективное чтение фиксированного количества байт (readFully):
public static int readFully(InputStream in, byte[] buf, int off, int len) throws IOException {
int total = 0;
while (total < len) {
int r = in.read(buf, off + total, len - total);
if (r == -1) break;
total += r;
}
return total; // может быть меньше len при EOF
}
// Использование с BufferedInputStream для производительности
Результат: Гарантированное чтение до len байт или меньше при EOF
4) Кастомизация размера буфера для сетевых приложений и тесты производительности:
// Тесты показывают оптимальный размер буфера для конкретной платформы и сети
int[] sizes = {1024, 4096, 8192, 65536};
for (int s : sizes) {
long t0 = System.nanoTime();
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("big.dat"), s)){
while (bis.read() != -1) {}
}
long t1 = System.nanoTime();
System.out.println(s + ": " + (t1 - t0)/1_000_000 + " ms");
}
Результат: Выдаёт время чтения для каждого размера буфера; помогает выбрать лучший размер в конкретной среде
5) Реюз внутреннего буфера через обёртку с пулом буферов (снижение аллокаций в горячих путях):
// Идея: переиспользовать массивы байт при создании BufferedInputStream через фабричный метод
// Концептуальный пример, реализация требует аккуратной синхронизации и контроля времени жизни
Результат: Снижение числа выделений памяти в интенсивных сценариях ввода/вывода
6) Совместное чтение и потоковая обработка: сочетание BufferedInputStream и java.util.zip.ZipInputStream позволяет эффективно находить и читать элементы в ZIP-архивах, избегая множественных малых чтений.