Buffer Basics
Understanding position, limit, and capacity is essential for working with buffers effectively.
Interactive Buffer Visualizer
Try it yourself! Write characters or strings, call resetForRead(), then read them back:
WRITE MODEremaining() = 10
[0]
—
[1]
—
[2]
—
[3]
—
[4]
—
[5]
—
[6]
—
[7]
—
[8]
—
[9]
—
position=0limit=10capacity=10
Position Readable Writable Beyond limit
Buffer allocated with capacity 10
Buffer State
Every buffer has three key properties:
- Position: Current read/write location (0 to limit-1)
- Limit: Boundary for read/write operations (0 to capacity)
- Capacity: Total buffer size (fixed at allocation)
Write Mode vs Read Mode
After Allocation (Write Mode)
val buffer = PlatformBuffer.allocate(10)
// position=0, limit=10, capacity=10
After allocation: ready to write
WRITE MODEremaining() = 10
[0]
—
[1]
—
[2]
—
[3]
—
[4]
—
[5]
—
[6]
—
[7]
—
[8]
—
[9]
—
position=0limit=10capacity=10
After Writing
buffer.writeString("HELLO")
// position=5, limit=10, capacity=10
After writeString('HELLO'): position advanced to 5
WRITE MODEremaining() = 5
[0]
H
48
[1]
E
45
[2]
L
4c
[3]
L
4c
[4]
O
4f
[5]
—
[6]
—
[7]
—
[8]
—
[9]
—
position=5limit=10capacity=10
After resetForRead() (Read Mode)
buffer.resetForRead()
// position=0, limit=5, capacity=10
After resetForRead(): ready to read 'HELLO'
READ MODEremaining() = 5
[0]
H
48
[1]
E
45
[2]
L
4c
[3]
L
4c
[4]
O
4f
[5]
—
[6]
—
[7]
—
[8]
—
[9]
—
position=0limit=5capacity=10
Key Properties
val buffer = PlatformBuffer.allocate(1024)
buffer.capacity // Total size (1024)
buffer.position() // Current position
buffer.limit() // Current limit
buffer.remaining() // limit - position (bytes available)
Manipulating Position and Limit
// Set position directly
buffer.position(100)
// Set limit
buffer.setLimit(500)
// Move position relative to current
buffer.position(buffer.position() + 10)
Interfaces
Buffer uses three main interfaces:
ReadBuffer
Read operations without modifying content:
val buffer: ReadBuffer = ...
val byte = buffer.readByte()
val int = buffer.readInt()
val slice = buffer.slice() // Create a view
WriteBuffer
Write operations:
val buffer: WriteBuffer = ...
buffer.writeByte(0x42)
buffer.writeInt(12345)
buffer.write(otherBuffer)
PlatformBuffer
Combines both interfaces with platform-specific extensions:
val buffer: PlatformBuffer = PlatformBuffer.allocate(1024)
buffer.writeInt(42)
buffer.resetForRead()
val value = buffer.readInt()
Slicing
Create a view into a buffer without copying:
val original = PlatformBuffer.allocate(10)
original.writeString("HELLOWORLD")
original.resetForRead()
// Create a slice of bytes 2-7 ("LLOWO")
original.position(2)
original.setLimit(7)
val slice = original.slice()
// slice shares the same memory
// Modifications to slice affect original
Slice from position 2 to limit 7
READ MODEremaining() = 5
[0]
H
48
[1]
E
45
[2]
L
4c
[3]
L
4c
[4]
O
4f
[5]
W
57
[6]
O
4f
[7]
R
52
[8]
L
4c
[9]
D
44
slice()
position=2limit=7capacity=10
Why slicing matters:
- Zero allocation - No new memory is allocated
- O(1) operation - Constant time regardless of size
- Zero-copy - Data is never copied, just a new view
- Memory efficient - Perfect for parsing protocols where you need sub-ranges
Absolute vs Relative Operations
Relative (modifies position)
buffer.writeInt(42) // Writes at position, advances by 4
buffer.readInt() // Reads at position, advances by 4
Absolute (position unchanged)
buffer[10] = 0x42.toByte() // Write at index 10
val byte = buffer[10] // Read from index 10
val int = buffer.getInt(10) // Read int at index 10
Best Practices
- Always call
resetForRead()after writing, before reading - Use
remaining()to check available bytes before reading - Prefer slicing over copying for zero-copy performance
- Use buffer pools in hot paths to avoid allocation overhead