BufferedInputStream
Reading a file one byte at a time triggers a system call for every single byte — that is thousands of expensive kernel transitions for even a small file. BufferedInputStream fixes this by wrapping any InputStream and pulling data in large chunks into an internal byte array (the buffer), so your code reads from fast memory instead of the disk or network most of the time.
What BufferedInputStream Does
BufferedInputStream is a decorator — it wraps another InputStream and adds buffering on top. The class lives in java.io and extends FilterInputStream.
When you call read(), the buffer is filled in one shot (default 8 KB) from the underlying stream. Subsequent read() calls drain that in-memory buffer without touching the OS until the buffer is exhausted, at which point one more fill happens. The net effect: far fewer system calls for the same amount of data.
Tip: Always wrap file-based streams with
BufferedInputStreamunless you have a specific reason not to. The performance gain for sequential reads is usually 10–100×.
Basic Usage
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class BasicBufferedRead {
public static void main(String[] args) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("data.txt"))) {
int byteRead;
while ((byteRead = bis.read()) != -1) {
System.out.print((char) byteRead);
}
}
}
}
The try-with-resources block guarantees the stream is closed even if an exception is thrown. read() returns -1 at end-of-stream — a contract inherited from InputStream.
Reading into a Byte Array (Bulk Read)
Reading one byte at a time is still suboptimal even with buffering. Use read(byte[], int, int) to pull multiple bytes in a single call:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class BulkRead {
public static void main(String[] args) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("image.png"))) {
byte[] chunk = new byte[4096];
int bytesRead;
long total = 0;
while ((bytesRead = bis.read(chunk, 0, chunk.length)) != -1) {
total += bytesRead;
// process chunk[0..bytesRead-1]
}
System.out.println("Total bytes read: " + total);
}
}
}
Output:
Total bytes read: 204800
Note:
read(byte[], offset, length)returns the number of bytes actually read, which may be less thanlengtheven when more data is available. Always use the returned count, notlength, when processing the array.
Custom Buffer Size
The default buffer is 8192 bytes (8 KB). You can specify a different size in the constructor:
// 64 KB buffer — beneficial for high-throughput network or disk reads
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("large-log.bin"), 65536);
Larger buffers reduce the number of fill operations but consume more heap. For most file I/O, the default 8 KB is a sensible starting point. For network streams or large sequential files, 32–256 KB can be more efficient.
mark() and reset()
BufferedInputStream supports the mark(int readLimit) / reset() contract, which lets you re-read a section of the stream:
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class MarkResetDemo {
public static void main(String[] args) throws IOException {
byte[] data = {10, 20, 30, 40, 50};
try (BufferedInputStream bis = new BufferedInputStream(
new ByteArrayInputStream(data))) {
System.out.println(bis.read()); // 10
bis.mark(10); // mark position after first byte
System.out.println(bis.read()); // 20
System.out.println(bis.read()); // 30
bis.reset(); // jump back to the mark
System.out.println(bis.read()); // 20 again
}
}
}
Output:
10
20
30
20
The readLimit argument to mark() tells BufferedInputStream how many bytes you might want to re-read. If you read more than that before calling reset(), the mark may be invalidated and reset() will throw an IOException.
Warning: The underlying stream does not need to support
mark()/reset().BufferedInputStreamimplements the feature entirely in its own buffer. But once you read pastreadLimitbytes, all bets are off.
Checking markSupported()
Always verify before calling mark() on an unknown stream:
if (bis.markSupported()) {
bis.mark(256);
// ... read up to 256 bytes
bis.reset();
}
BufferedInputStream.markSupported() always returns true.
Constructors at a Glance
| Constructor | Buffer Size |
|---|---|
BufferedInputStream(InputStream in) | 8192 bytes (default) |
BufferedInputStream(InputStream in, int size) | Custom size bytes |
Under the Hood
Internally, BufferedInputStream holds:
byte[] buf— the internal buffer arrayint count— how many valid bytes are in the bufferint pos— the next byte to return from the bufferint markpos— position at whichmark()was called (-1 if not set)int marklimit— thereadLimitpassed tomark()
When pos >= count (buffer exhausted), the fill() private method is invoked. It calls the underlying stream’s read(buf, 0, buf.length) to refill in one shot. This is the single system call that replaces what would have been buf.length individual calls on an unbuffered stream.
The class is not thread-safe. If two threads share a BufferedInputStream, reads and the pos/count fields can get out of sync, causing data corruption. Protect access with synchronized blocks or use a different approach (e.g., one thread owns the stream).
Since Java 9, BufferedInputStream uses sun.misc.Unsafe-backed field access for the pos and count fields to minimize volatile overhead — though this is an implementation detail that can change.
BufferedInputStream vs FileInputStream
| Feature | FileInputStream | BufferedInputStream wrapping FileInputStream |
|---|---|---|
Each read() call | System call to OS | Served from memory (usually) |
| Suitable for bulk reads | Yes, with byte array | Yes, even faster |
mark()/reset() | Not supported | Supported |
| Overhead | Minimal per-object | ~8 KB heap for the buffer |
For reading byte streams sequentially, the wrapped version wins almost every time.
Closing the Stream
Closing BufferedInputStream also closes the underlying InputStream. Always use try-with-resources (Java 7+) to ensure cleanup:
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("notes.txt"))) {
// use bis
} // bis.close() called automatically, even on exception
Tip: If you need to read text (not binary) data, consider wrapping a
FileInputStreamwith an InputStreamReader and then a BufferedReader instead.BufferedReaderaddsreadLine()and handles character encoding automatically.
Practical Example: Copying a File
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) throws IOException {
String src = "source.bin";
String dest = "destination.bin";
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) != -1) {
out.write(buf, 0, n);
}
}
System.out.println("File copied successfully.");
}
}
Output:
File copied successfully.
Pairing BufferedInputStream with BufferedOutputStream gives you buffering on both ends of the pipe — reads and writes both hit memory most of the time.
Related Topics
- FileInputStream — the unbuffered byte stream that BufferedInputStream typically wraps
- BufferedOutputStream — the write-side counterpart for buffered byte output
- BufferedReader — buffered character-stream reading with
readLine()support - Byte vs Character Streams — choosing the right stream family for your data
- InputStreamReader — bridge from bytes to characters with explicit charset control
- Java I/O — overview of the entire java.io package