Scatter/Gather(分散/聚集) 是 NIO 通道的進階讀寫模式,允許單次 I/O 操作跨越多個緩衝區,配合作業系統原生矢量 I/O(readv/writev)實現零拷貝高效傳輸。本文詳細解析 ScatteringByteChannel/GatheringByteChannel 介面、分散讀取與聚集寫入流程、position/limit 控制、子集操作、效能優勢與實戰應用場景。
read(Buffer[]) 將通道資料依序填滿多個緩衝區,直到資料耗盡或緩衝區無空間write(Buffer[]) 將多個緩衝區資料依序串聯寫入通道readv/writev 系統呼叫,減少用戶態/核心態拷貝與系統呼叫次數public interface ScatteringByteChannel extends ReadableByteChannel {
long read(ByteBuffer[] dsts) throws IOException;
long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
}
public interface GatheringByteChannel extends WritableByteChannel {
long write(ByteBuffer[] srcs) throws IOException;
long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
}
offset/length 參數:操作緩衝區陣列的子範圍。offset 是陣列索引,length 是緩衝區數量。
ByteBuffer[] buffers = {buf0, buf1, buf2, buf3, buf4};
// 僅寫入 buf1, buf2, buf3(索引 1 開始,共 3 個)
channel.write(buffers, 1, 3);
典型場景:協議幀 = 固定長度 Header + 變長 Body
ByteBuffer header = ByteBuffer.allocate(128); // 協議頭
ByteBuffer body = ByteBuffer.allocate(1024); // 協議體
// 填充資料
header.putInt(0xCAFEBABE).putInt(100).putLong(System.currentTimeMillis());
body.put("Hello Scatter/Gather!".getBytes());
header.flip(); body.flip();
ByteBuffer[] buffers = {header, body};
channel.write(buffers); // 一次系統呼叫發送 Header+Body
position/limit 決定實際寫入範圍position 到 limit 之間的資料會被寫入header.position=0, limit=58 → 僅寫入前 58 位元組ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {header, body};
channel.read(buffers); // 依序填充 header 再 body
// 讀取後自動更新各 buffer 的 position
// 假設通道有 200 位元組:
// header 滿 128 位元組,body 寫入 72 位元組
// header.limit=128, body.position=72
| 維度 | 傳統循環讀寫 | Scatter/Gather |
|---|---|---|
| 系統呼叫次數 | N 次(N = 緩衝區數) | 1 次 readv/writev |
| 用戶態/核心態拷貝 | N 次 | 1 次(OS 直接填充/讀取多緩衝區) |
| 上下文切換 | N 次 | 1 次 |
| 程式碼複雜度 | 需手動循環 | 單行 API |
| 協議解析 | 需手動拆分 | 天然對應 Header/Body 結構 |
ByteBuffer.allocateDirect()),避免 JVM 堆到堆外的額外拷貝readv/writev(Linux、Windows、macOS 均支援)IOV_MAX 限制,通常 1024)write 刷盤FileChannel.transferTo() 實現檔案到網路直通read/write 返回實際傳輸位元組數,可能小於總剩餘空間/資料,需檢查返回值並處理殘留flip()(寫入前)或處理後 clear()/compact()(讀取後)read/write 可能返回 0,需配合 Selector 輪詢offset/length 避免陣列複製package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ScatterGatherDemo {
public static void main(String[] args) throws IOException {
// 簡易 Echo Server 演示 Scatter/Gather
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8888));
ssc.configureBlocking(true);
System.out.println("Server started on 8888");
SocketChannel client = ssc.accept();
// 分配 Header(8B) + Body(1024B)
ByteBuffer header = ByteBuffer.allocateDirect(8);
ByteBuffer body = ByteBuffer.allocateDirect(1024);
ByteBuffer[] buffers = {header, body};
while (true) {
// Scatter 讀取:先填 Header 再填 Body
long read = client.read(buffers);
if (read == -1) break; // 客戶端關閉
header.flip(); body.flip();
// 模擬處理:打印 Header 長度
if (header.remaining() >= 4) {
int bodyLen = header.getInt();
System.out.println("Received frame, body length: " + bodyLen);
}
// Gather 寫回(Echo)
header.rewind(); body.rewind(); // 重置 position=0 供寫入
client.write(buffers);
header.clear(); body.clear(); // 準備下一輪
}
client.close();
ssc.close();
}
}
Scatter/Gather 將多緩衝區 I/O 融合為單次系統呼叫,完美契合「Header+Body」結構化資料傳輸,配合直接緩衝區可實現真正的零拷貝高效能。掌握 ScatteringByteChannel/GatheringByteChannel API、position/limit 控制、子集操作,是構建高效網路伺服器、檔案處理器的關鍵技能。