15.3 Scattering, Gathering, and Composed Buffers
Master advanced buffer patterns for efficient structured data handling.
Scattering Reads
Basic Scatter Reading:
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
/**
* Read data directly into multiple buffers
*/
public static void scatterRead() throws IOException {
// Create multiple buffers for different message parts
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer trailer = ByteBuffer.allocate(256);
ByteBuffer[] buffers = {header, body, trailer};
try (SocketChannel channel = SocketChannel.open()) {
channel.connect(new InetSocketAddress("localhost", 8080));
// Read entire message into appropriate buffers
long bytesRead = channel.read(buffers);
System.out.println("Total bytes read: " + bytesRead);
System.out.println("Header position: " + header.position());
System.out.println("Body position: " + body.position());
System.out.println("Trailer position: " + trailer.position());
}
}
/**
* Partial scatter read
*/
public static void partialScatterRead() throws IOException {
ByteBuffer[] buffers = {
ByteBuffer.allocate(256),
ByteBuffer.allocate(512),
ByteBuffer.allocate(256)
};
try (SocketChannel channel = SocketChannel.open()) {
channel.connect(new InetSocketAddress("localhost", 8080));
// Read into buffers[1] and buffers[2] only
long bytesRead = channel.read(buffers, 1, 2);
System.out.println("Bytes read into buffers[1] and [2]: " + bytesRead);
System.out.println("buffers[0] position (unchanged): " + buffers[0].position());
System.out.println("buffers[1] position: " + buffers[1].position());
System.out.println("buffers[2] position: " + buffers[2].position());
}
}
Protocol Parsing with Scatter:
/**
* Parse message protocol: [length(4)][type(1)][data(var)][checksum(4)]
*/
public class ProtocolParser {
private static final int HEADER_SIZE = 5;
private static final int CHECKSUM_SIZE = 4;
/**
* Read protocol message with scatter
*/
public static ProtocolMessage readMessage(SocketChannel channel)
throws IOException {
// Prepare buffers for protocol fields
ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE);
ByteBuffer dataBuffer = null;
ByteBuffer checksum = ByteBuffer.allocate(CHECKSUM_SIZE);
// First scatter: read header
while (header.hasRemaining()) {
channel.read(header);
}
// Parse header
header.flip();
int dataLength = header.getInt();
byte type = header.get();
// Allocate buffer for data
dataBuffer = ByteBuffer.allocate(dataLength);
// Second scatter: read data and checksum
ByteBuffer[] buffers = {dataBuffer, checksum};
long totalRead = 0;
while (totalRead < (dataLength + CHECKSUM_SIZE)) {
totalRead += channel.read(buffers);
}
// Parse message
dataBuffer.flip();
byte[] data = new byte[dataBuffer.remaining()];
dataBuffer.get(data);
checksum.flip();
int checksumValue = checksum.getInt();
return new ProtocolMessage(type, data, checksumValue);
}
public static class ProtocolMessage {
public final byte type;
public final byte[] data;
public final int checksum;
public ProtocolMessage(byte type, byte[] data, int checksum) {
this.type = type;
this.data = data;
this.checksum = checksum;
}
}
}
Gathering Writes
Basic Gather Writing:
/**
* Write multiple buffers in single operation
*/
public static void gatherWrite() throws IOException {
ByteBuffer header = ByteBuffer.wrap(new byte[]{0x01, 0x02, 0x03});
ByteBuffer body = ByteBuffer.wrap("Hello, World!".getBytes());
ByteBuffer trailer = ByteBuffer.wrap(new byte[]{0xFF, 0xFE});
ByteBuffer[] buffers = {header, body, trailer};
try (FileChannel out = FileChannel.open(Path.of("output.bin"),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
// Write all buffers in order
long bytesWritten = out.write(buffers);
System.out.println("Bytes written: " + bytesWritten);
}
}
/**
* Partial gather write
*/
public static void partialGatherWrite() throws IOException {
ByteBuffer[] buffers = {
ByteBuffer.wrap(new byte[]{0x01}),
ByteBuffer.wrap(new byte[]{0x02}),
ByteBuffer.wrap(new byte[]{0x03}),
ByteBuffer.wrap(new byte[]{0x04})
};
try (FileChannel out = FileChannel.open(Path.of("output.bin"),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
// Write only buffers[1] and buffers[2]
long bytesWritten = out.write(buffers, 1, 2);
System.out.println("Bytes written: " + bytesWritten); // 2
}
}
Building Messages with Gather:
/**
* Construct and send protocol message
*/
public class MessageBuilder {
/**
* Build message with header, body, footer
*/
public static void sendStructuredMessage(SocketChannel channel,
String body) throws IOException {
// Header: message length and type
ByteBuffer header = ByteBuffer.allocate(5);
header.putInt(body.length());
header.put((byte) 0x01); // Type
header.flip();
// Body
ByteBuffer bodyBuf = ByteBuffer.wrap(body.getBytes());
// Trailer: checksum
ByteBuffer trailer = ByteBuffer.allocate(4);
int checksum = calculateChecksum(body.getBytes());
trailer.putInt(checksum);
trailer.flip();
// Send all at once
ByteBuffer[] buffers = {header, bodyBuf, trailer};
long bytesWritten = 0;
while (bytesWritten < header.capacity() + bodyBuf.capacity() + trailer.capacity()) {
bytesWritten += channel.write(buffers);
}
System.out.println("Message sent: " + bytesWritten + " bytes");
}
private static int calculateChecksum(byte[] data) {
int sum = 0;
for (byte b : data) {
sum += b & 0xFF;
}
return sum;
}
public static void main(String[] args) throws IOException {
// Example: send message
try (SocketChannel channel = SocketChannel.open()) {
channel.connect(new InetSocketAddress("localhost", 8080));
sendStructuredMessage(channel, "Hello, Protocol!");
}
}
}
Composed Buffers
Combining Multiple Buffers:
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
/**
* Logically combine multiple buffers
*/
public class ComposedBufferExample {
/**
* Create composed buffer from multiple sources
*/
public static ByteBuffer compose(ByteBuffer... buffers) throws IOException {
// Calculate total size
int totalSize = 0;
for (ByteBuffer buf : buffers) {
totalSize += buf.remaining();
}
// Create result buffer
ByteBuffer result = ByteBuffer.allocate(totalSize);
// Copy all buffers
for (ByteBuffer buf : buffers) {
result.put(buf);
}
result.flip();
return result;
}
/**
* Copy buffer and append
*/
public static ByteBuffer append(ByteBuffer original, ByteBuffer additional) {
ByteBuffer combined = ByteBuffer.allocate(
original.remaining() + additional.remaining()
);
combined.put(original);
combined.put(additional);
combined.flip();
return combined;
}
/**
* Prepend to buffer
*/
public static ByteBuffer prepend(ByteBuffer prefix, ByteBuffer original) {
ByteBuffer combined = ByteBuffer.allocate(
prefix.remaining() + original.remaining()
);
combined.put(prefix);
combined.put(original);
combined.flip();
return combined;
}
/**
* Insert into buffer
*/
public static ByteBuffer insert(ByteBuffer buffer, int position,
ByteBuffer insertion) {
ByteBuffer combined = ByteBuffer.allocate(
buffer.remaining() + insertion.remaining()
);
// Copy before insertion point
int originalPos = buffer.position();
buffer.position(0);
buffer.limit(position);
combined.put(buffer);
// Insert new data
combined.put(insertion);
// Copy after insertion point
buffer.position(position);
buffer.limit(buffer.capacity());
combined.put(buffer);
buffer.position(originalPos);
combined.flip();
return combined;
}
}
Logical Buffer View:
/**
* Create logical view of multiple buffers as single buffer
*/
public class CompositeByteBuffer {
private final ByteBuffer[] buffers;
private int position = 0;
private int limit;
public CompositeByteBuffer(ByteBuffer... buffers) {
this.buffers = buffers;
this.limit = calculateTotal();
}
private int calculateTotal() {
int total = 0;
for (ByteBuffer buf : buffers) {
total += buf.remaining();
}
return total;
}
/**
* Get next byte without advancing position
*/
public byte peek() {
if (position >= limit) {
throw new BufferUnderflowException();
}
int bufferIndex = findBufferIndex(position);
int offsetInBuffer = position - getBufferStartPosition(bufferIndex);
return buffers[bufferIndex].get(offsetInBuffer);
}
/**
* Get next byte and advance position
*/
public byte get() {
byte value = peek();
position++;
return value;
}
/**
* Get multiple bytes
*/
public void get(byte[] dst) {
for (int i = 0; i < dst.length; i++) {
dst[i] = get();
}
}
/**
* Advance position
*/
public void skip(int bytes) {
position = Math.min(position + bytes, limit);
}
/**
* Get current position
*/
public int position() {
return position;
}
/**
* Set position
*/
public void position(int newPosition) {
if (newPosition < 0 || newPosition > limit) {
throw new IllegalArgumentException("Invalid position: " + newPosition);
}
this.position = newPosition;
}
/**
* Get total size
*/
public int remaining() {
return limit - position;
}
private int findBufferIndex(int position) {
int accumulator = 0;
for (int i = 0; i < buffers.length; i++) {
int bufferSize = buffers[i].remaining();
if (position < accumulator + bufferSize) {
return i;
}
accumulator += bufferSize;
}
throw new IndexOutOfBoundsException("Position: " + position);
}
private int getBufferStartPosition(int bufferIndex) {
int accumulator = 0;
for (int i = 0; i < bufferIndex; i++) {
accumulator += buffers[i].remaining();
}
return accumulator;
}
/**
* Usage example
*/
public static void main(String[] args) {
ByteBuffer buf1 = ByteBuffer.wrap("Hello, ".getBytes());
ByteBuffer buf2 = ByteBuffer.wrap("World".getBytes());
ByteBuffer buf3 = ByteBuffer.wrap("!".getBytes());
CompositeByteBuffer composite = new CompositeByteBuffer(buf1, buf2, buf3);
System.out.println("Remaining: " + composite.remaining()); // 13
// Read bytes one by one
while (composite.remaining() > 0) {
System.out.print((char) composite.get());
}
System.out.println();
// Reset and peek
composite.position(0);
System.out.println("First char: " + (char) composite.peek());
System.out.println("Position after peek: " + composite.position()); // Still 0
// Read entire message
byte[] message = new byte[composite.remaining()];
composite.get(message);
System.out.println("Message: " + new String(message));
}
}
Typed Buffers Deep Dive
Working with Typed Buffers:
/**
* Efficient structured data with typed buffers
*/
public class StructuredData {
/**
* Record structure: 4 fields
* [id(int), value(long), score(float), active(byte)]
*/
public static class Record {
public int id;
public long value;
public float score;
public byte active;
public void writeTo(ByteBuffer buffer) {
buffer.putInt(id);
buffer.putLong(value);
buffer.putFloat(score);
buffer.put(active);
}
public static Record readFrom(ByteBuffer buffer) {
Record record = new Record();
record.id = buffer.getInt();
record.value = buffer.getLong();
record.score = buffer.getFloat();
record.active = buffer.get();
return record;
}
}
/**
* Write records using IntBuffer view
*/
public static void writeRecordsTyped() {
// Use IntBuffer for integer fields
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
IntBuffer intView = byteBuffer.asIntBuffer();
LongBuffer longView = byteBuffer.asLongBuffer();
Record record1 = new Record();
record1.id = 1;
record1.value = 1000L;
record1.score = 95.5f;
record1.active = 1;
// Write using byte buffer
byteBuffer.clear();
record1.writeTo(byteBuffer);
byteBuffer.flip();
// Read back using views
intView.position(0);
System.out.println("ID from IntView: " + intView.get());
}
/**
* Batch operations with typed buffers
*/
public static void batchProcessing() {
ByteBuffer buffer = ByteBuffer.allocate(256);
IntBuffer intBuffer = buffer.asIntBuffer();
// Write batch of integers
int[] values = {10, 20, 30, 40, 50};
for (int value : values) {
intBuffer.put(value);
}
// Read back
intBuffer.flip();
int[] readValues = new int[intBuffer.remaining()];
intBuffer.get(readValues);
System.out.println("Read values: " + Arrays.toString(readValues));
}
}
CharBuffer for Text:
/**
* Text processing with CharBuffer
*/
public class TextBuffering {
/**
* Wrap string as CharBuffer
*/
public static CharBuffer wrapString(String text) {
return CharBuffer.wrap(text);
}
/**
* Build string in buffer
*/
public static String buildString(CharBuffer buffer) {
if (buffer.hasArray()) {
return new String(buffer.array(), buffer.arrayOffset(), buffer.remaining());
}
char[] chars = new char[buffer.remaining()];
buffer.get(chars);
return new String(chars);
}
/**
* Line-by-line text processing
*/
public static void processText(String text) {
CharBuffer buffer = CharBuffer.wrap(text);
StringBuilder line = new StringBuilder();
while (buffer.hasRemaining()) {
char ch = buffer.get();
if (ch == '\n') {
System.out.println("Line: " + line);
line = new StringBuilder();
} else {
line.append(ch);
}
}
if (line.length() > 0) {
System.out.println("Line: " + line);
}
}
}
Best Practices
1. Use Scatter/Gather for Protocol Parsing:
// Separate concerns: header, body, trailer
ByteBuffer[] buffers = {headerBuf, bodyBuf, trailerBuf};
channel.read(buffers);
2. Compose Buffers Logically:
// Keep buffers separate and compose on demand
ByteBuffer composed = compose(buf1, buf2, buf3);
3. Choose Correct Buffer Type:
// Use typed buffers for efficiency
IntBuffer ints = intBuffer.asIntBuffer();
LongBuffer longs = byteBuffer.asLongBuffer();
4. Handle Partial Operations:
// May not read/write entire array
long totalRead = 0;
while (totalRead < expectedSize) {
totalRead += channel.read(buffers);
}
5. Maintain Buffer Position Tracking:
// Know where data is in composed buffers
int bufferIndex = findBufferContaining(position);
These scattering, gathering, and composition techniques enable efficient structured data handling in modern NIO applications.