Card 05/ 08
Decorator in the Wild: Java's BufferedInputStream
new BufferedInputStream(new FileInputStream(...)) is the pattern written out in one line.
Java's java.io package is built on the Decorator pattern, and BufferedInputStream is the example most Java code meets first. A FileInputStream reads bytes from a file, and wrapping it in a BufferedInputStream adds a buffer: the wrapper reads a large block at once and hands bytes out from memory. Both are InputStreams, so code that reads a stream accepts either.
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class DeBuffered {
public static void main(String[] args) throws IOException {
Path file = Files.createTempFile("orders", ".txt");
Files.write(file, new byte[10_000]);
CountingInputStream unbuffered = new CountingInputStream(new FileInputStream(file.toFile()));
System.out.println("unbuffered: " + readAll(unbuffered) + " bytes, " + unbuffered.calls + " reads from the file");
CountingInputStream counted = new CountingInputStream(new FileInputStream(file.toFile()));
InputStream buffered = new BufferedInputStream(counted);
System.out.println("buffered: " + readAll(buffered) + " bytes, " + counted.calls + " reads from the file");
Files.delete(file);
}
static int readAll(InputStream in) throws IOException {
int total = 0;
try (in) {
while (in.read() != -1) {
total++;
}
}
return total;
}
}
class CountingInputStream extends FilterInputStream {
int calls;
CountingInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
calls++;
return super.read();
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
calls++;
return super.read(buffer, offset, length);
}
}unbuffered: 10000 bytes, 10001 reads from the file
buffered: 10000 bytes, 3 reads from the file| Participant | java.io class |
|---|---|
| Component | InputStream |
| ConcreteComponent | FileInputStream |
| Decorator | FilterInputStream, which holds an InputStream and passes calls to it |
| ConcreteDecorator | BufferedInputStream, DataInputStream, and the listing's CountingInputStream |
The listing reads a 10,000-byte file one byte at a time, twice. Without the buffer, every read() reaches the file: 10,001 reads, the last one reporting the end. With the buffer in between, only three reads reach the file on eclipse-temurin:21: two blocks, of 8,192 bytes and of the remaining 1,808, and the read that finds the end. The 8,192 is the buffer's default size, and the code reading the stream did not change at all.
The counter is itself a decorator: it extends FilterInputStream, the pattern's abstract decorator in java.io, and overrides only the two read methods. Stacking it under the buffer is what lets the listing count what the buffer saves.
← → move through sections and cards