Buffer
10 mins
The Buffer class is the abstract base class for all NIO buffers, providing the core functionality for position, limit, and capacity management.
Source Code #
Core Implementation #
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
// Creates a new buffer with the given mark, position, limit, and capacity
Buffer(int mark, int pos, int lim, int cap) {
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("Mark > position: (" + mark + " > " + pos + ")");
this.mark = mark;
}
}
}
Implementation Details #
Core State Management #
// Returns this buffer's capacity
public final int capacity() {
return capacity;
}
// Returns this buffer's position
public final int position() {
return position;
}
// Sets this buffer's position
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
// Returns this buffer's limit
public final int limit() {
return limit;
}
// Sets this buffer's limit
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
// Sets this buffer's mark at its position
public final Buffer mark() {
mark = position;
return this;
}
// Resets this buffer's position to the previously-marked position
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
// Clears this buffer (position=0, limit=capacity, mark=-1)
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
// Flips this buffer (limit=position, position=0, mark=-1)
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
// Rewinds this buffer (position=0, mark=-1)
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
// Returns the number of elements between the current position and the limit
public final int remaining() {
return limit - position;
}
// Tells whether there are any elements between the current position and the limit
public final boolean hasRemaining() {
return position < limit;
}
// Tells whether this buffer is read-only
public abstract boolean isReadOnly();
Invariant Management #
The Buffer class maintains these invariants:
mark <= position <= limit <= capacity
All operations that modify these values enforce the invariants:
// Example from position() method
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1; // Invalidate mark if beyond new position
return this;
}
Buffer Comparison #
// Compares this buffer to another object
public int compareTo(Buffer other) {
int n = this.position() - other.position();
if (n != 0)
return n;
n = this.limit() - other.limit();
if (n != 0)
return n;
n = this.capacity() - other.capacity();
if (n != 0)
return n;
return 0;
}
// Tells whether or not this buffer is equal to another object
public boolean equals(Object ob) {
if (this == ob)
return true;
if (!(ob instanceof Buffer))
return false;
Buffer that = (Buffer)ob;
if (this.remaining() != that.remaining())
return false;
int p = this.position();
for (int i = 0; i < this.remaining(); i++)
if (!equals(this.get(p + i), that.get(that.position() + i)))
return false;
return true;
}
// Abstract equals method for element comparison
abstract boolean equals(Object ob1, Object ob2);
Hash Code Calculation #
// Computes the hash code of this buffer
public int hashCode() {
int h = 1;
int p = position;
for (int i = limit - 1; i >= p; i--)
h = h * 31 + hashCode(get(i));
return h;
}
// Abstract hashCode method for element hashing
abstract int hashCode(Object obj);
String Representation #
// Returns a string summarizing the state of this buffer
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName());
sb.append("[pos=");
sb.append(position());
sb.append(" lim=");
sb.append(limit());
sb.append(" cap=");
sb.append(capacity());
sb.append("]");
return sb.toString();
}
Buffer Operations #
Core Operations #
clear(): Prepares buffer for writing
position = 0,limit = capacity,mark = -1
flip(): Prepares buffer for reading
limit = position,position = 0,mark = -1
rewind(): Prepares for re-reading
position = 0,mark = -1(limit unchanged)
mark(): Marks current position
mark = position
reset(): Returns to marked position
position = mark
Data Transfer #
// Relative get operation
public abstract Object get();
// Absolute get operation
public abstract Object get(int index);
// Relative put operation
public abstract Buffer put(Object o);
// Absolute put operation
public abstract Buffer put(int index, Object o);
Bulk Operations #
// Relative bulk get operation
public Buffer get(Object[] dst, int offset, int length) {
checkBounds(offset, length, dst.length);
if (length > remaining())
throw new BufferUnderflowException();
for (int i = offset; i < offset + length; i++)
dst[i] = get();
return this;
}
// Relative bulk put operation
public final Buffer put(Object[] src, int offset, int length) {
checkBounds(offset, length, src.length);
if (length > remaining())
throw new BufferOverflowException();
for (int i = offset; i < offset + length; i++)
put(src[i]);
return this;
}
// Checks if the given bounds are valid
private static void checkBounds(int off, int len, int size) {
if ((off | len | (off + len) | (size - (off + len))) < 0)
throw new IndexOutOfBoundsException();
}
Concrete Buffer Implementations #
ByteBuffer #
The most commonly used buffer implementation:
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
// Factory methods
public static ByteBuffer allocate(int capacity) {
return new HeapByteBuffer(capacity, capacity);
}
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
public static ByteBuffer wrap(byte[] array) {
return new HeapByteBuffer(array, 0, array.length);
}
public static ByteBuffer wrap(byte[] array, int offset, int length) {
return new HeapByteBuffer(array, offset, length);
}
}
CharBuffer #
Character buffer for text processing:
public abstract class CharBuffer extends Buffer implements CharSequence, Comparable<CharBuffer> {
// Factory methods
public static CharBuffer allocate(int capacity) {
return new HeapCharBuffer(capacity, capacity);
}
public static CharBuffer wrap(CharSequence csq) {
return wrap(csq.toString());
}
public static CharBuffer wrap(char[] array) {
return new HeapCharBuffer(array, 0, array.length);
}
public static CharBuffer wrap(char[] array, int offset, int length) {
return new HeapCharBuffer(array, offset, length);
}
}
View Buffers #
Buffers that provide views of byte buffers as other primitive types:
// IntBuffer - views byte buffer as int sequence
public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> {
// Factory methods
public static IntBuffer allocate(int capacity) {
return new HeapIntBuffer(capacity, capacity);
}
public static IntBuffer wrap(int[] array) {
return new HeapIntBuffer(array, 0, array.length);
}
}
// Similar for LongBuffer, FloatBuffer, DoubleBuffer, ShortBuffer
Buffer Allocation Strategies #
Heap Buffers #
// Allocated in JVM heap, subject to GC
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
// Characteristics:
// - Allocated in JVM heap memory
// - Subject to garbage collection
// - Faster allocation
// - Slower I/O operations (requires copying to native memory)
Direct Buffers #
// Allocated in native memory, outside JVM heap
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
// Characteristics:
// - Allocated in native memory (outside JVM heap)
// - Not subject to garbage collection
// - Slower allocation
// - Faster I/O operations (zero-copy)
// - Requires explicit cleanup (uses Cleaner/PhantomReference)
Wrapped Buffers #
// Wraps existing arrays
byte[] array = new byte[1024];
ByteBuffer wrappedBuffer = ByteBuffer.wrap(array);
// Characteristics:
// - Shares the underlying array
// - Changes to buffer affect the array and vice versa
// - No memory copying overhead
Performance Considerations #
Buffer Type Selection #
| Buffer Type | Allocation Speed | I/O Speed | Memory Overhead | GC Impact |
|---|---|---|---|---|
| Heap | Fast | Slower | Low | High |
| Direct | Slow | Fast | High | None |
| Wrapped | N/A | Medium | None | Medium |
When to Use Each Type #
Heap Buffers:
- Short-lived buffers
- Small to medium sized buffers
- When allocation speed is critical
- When working with arrays anyway
Direct Buffers:
- Long-lived buffers
- Large buffers (>64KB)
- Frequent I/O operations
- Zero-copy requirements
- Native memory access needed
Wrapped Buffers:
- Existing array data
- Interoperability with array-based APIs
- Avoiding data copying
Common Buffer Patterns #
Read-Process-Write Cycle #
// Allocate buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// Read from channel
while (channel.read(buffer) != -1) {
buffer.flip(); // Prepare for reading
// Process data
while (buffer.hasRemaining()) {
byte b = buffer.get();
// Process byte
}
buffer.clear(); // Prepare for next read
}
Scattering Reads #
// Create buffer array for scattering read
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(10240);
ByteBuffer[] buffers = {header, body};
// Read into multiple buffers
channel.read(buffers);
// Process each buffer
for (ByteBuffer buf : buffers) {
buf.flip();
// Process buffer contents
}
Gathering Writes #
// Create buffer array for gathering write
ByteBuffer header = ByteBuffer.wrap("HEADER".getBytes());
ByteBuffer body = ByteBuffer.wrap("BODY".getBytes());
ByteBuffer[] buffers = {header, body};
// Write from multiple buffers
channel.write(buffers);
Buffer Slicing #
// Create a slice of existing buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// Fill buffer with data...
buffer.position(256).limit(768);
ByteBuffer slice = buffer.slice();
// Slice shares data with original buffer
slice.put(0, (byte)42); // Modifies original buffer too
Duplicate Buffers #
// Create independent buffer with same content
ByteBuffer original = ByteBuffer.allocate(1024);
// Fill buffer with data...
ByteBuffer duplicate = original.duplicate();
// Duplicate has independent position/limit/mark
duplicate.position(512);
// Doesn't affect original's position
Best Practices #
- Reuse Buffers: Allocate buffers once and reuse them to reduce GC pressure
- Size Appropriately: Choose buffer sizes based on expected data volumes
- Use Direct Buffers: For large, long-lived buffers involved in frequent I/O
- Flip Properly: Always call
flip()before reading from a buffer you’ve written to - Clear Properly: Always call
clear()orcompact()before reusing a buffer - Check Capacity: Verify buffer capacity before operations to avoid exceptions
- Use Bulk Operations: Prefer bulk
get()/put()operations over element-by-element access - Monitor Memory: Direct buffers don’t show up in heap usage - monitor native memory
Common Pitfalls #
- Forgetting to Flip: Reading from a buffer without calling
flip()first - Buffer Overflow: Writing beyond capacity without checking
remaining() - Buffer Underflow: Reading beyond limit without checking
hasRemaining() - Memory Leaks: Not releasing direct buffers properly
- Thread Safety: Buffers are not thread-safe for concurrent access
- Endianness: Forgetting to set byte order for multi-byte data
- Mark Invalid: Using
reset()without first callingmark()
Buffer Internals #
HeapByteBuffer Implementation #
class HeapByteBuffer extends ByteBuffer {
final byte[] hb; // The underlying byte array
final int offset; // The offset into the array
HeapByteBuffer(int cap, int lim) {
super(-1, 0, lim, cap, null);
hb = new byte[cap];
offset = 0;
}
HeapByteBuffer(byte[] buf, int off, int len) {
super(-1, off, off + len, buf.length, null);
hb = buf;
offset = 0;
}
public byte get() {
return hb[ix(nextGetIndex())];
}
public byte get(int i) {
return hb[ix(checkIndex(i))];
}
public ByteBuffer put(byte x) {
hb[ix(nextPutIndex())] = x;
return this;
}
public ByteBuffer put(int i, byte x) {
hb[ix(checkIndex(i))] = x;
return this;
}
int ix(int i) {
return i + offset;
}
}
DirectByteBuffer Implementation #
class DirectByteBuffer extends ByteBuffer {
private final long address; // Native memory address
private final Cleaner cleaner; // For freeing native memory
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap, null);
address = UNSAFE.allocateMemory(cap);
cleaner = Cleaner.create(this, new Deallocator(address));
}
public byte get() {
return UNSAFE.getByte(address + ix(nextGetIndex()));
}
public byte get(int i) {
return UNSAFE.getByte(address + ix(checkIndex(i)));
}
public ByteBuffer put(byte x) {
UNSAFE.putByte(address + ix(nextPutIndex()), x);
return this;
}
public ByteBuffer put(int i, byte x) {
UNSAFE.putByte(address + ix(checkIndex(i)), x);
return this;
}
private static class Deallocator implements Runnable {
private static final long address;
Deallocator(long address) { this.address = address; }
public void run() {
UNSAFE.freeMemory(address);
}
}
}
Performance Optimization Techniques #
Buffer Pooling #
// Simple buffer pool
public class BufferPool {
private final BlockingQueue<ByteBuffer> pool;
public BufferPool(int size, int bufferSize) {
pool = new ArrayBlockingQueue<>(size);
for (int i = 0; i < size; i++) {
pool.add(ByteBuffer.allocateDirect(bufferSize));
}
}
public ByteBuffer acquire() throws InterruptedException {
ByteBuffer buffer = pool.take();
buffer.clear();
return buffer;
}
public void release(ByteBuffer buffer) {
buffer.clear();
pool.put(buffer);
}
}
Zero-Copy Techniques #
// Memory-mapped file for zero-copy I/O
FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size()
);
// Direct buffer to socket (zero-copy)
SocketChannel socketChannel = SocketChannel.open();
socketChannel.write(buffer);
Composite Buffers #
// Combine multiple buffers for single I/O operation
ByteBuffer[] buffers = new ByteBuffer[] {
ByteBuffer.wrap("HEADER".getBytes()),
dataBuffer,
ByteBuffer.wrap("FOOTER".getBytes())
};
// Single write operation for all buffers
channel.write(buffers);