Skip to main content

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

  1. Always call resetForRead() after writing, before reading
  2. Use remaining() to check available bytes before reading
  3. Prefer slicing over copying for zero-copy performance
  4. Use buffer pools in hot paths to avoid allocation overhead