Scatter与Gather不支持自动识别变长数据,需通过固定头+动态体结构配合显式控制实现;典型应用如读取前4字节长度字段再按需读body,或用Gather拼接HTTP响应多段Buffer。

Scatter 与 Gather 本身不直接支持“变量长度数据”的自动识别和分割,它们按 Buffer 容量顺序严格填充或写出。但你可以通过组合设计(比如固定头 + 动态体)+ 显式控制逻辑,安全、高效地处理变量数据。关键不是让 NIO 自动猜长度,而是用结构化方式把变量部分“框”进合适的 Buffer。
用 Scatter 处理带长度字段的变长消息
典型场景:网络协议中,前 4 字节是 body 长度(int),后续是不定长 payload。Scatter 可先读 header,再读 body,但需手动控制 body Buffer 容量和后续 flip/compact。
- 分配两个 Buffer:header = ByteBuffer.allocate(4)(固定大小),body = ByteBuffer.allocate(maxExpectedSize)(预留足够空间,不一定要精确)
- 调用 channel.read(new ByteBuffer[]{header, body}) —— NIO 先填满 header(4 字节),再把剩余可用字节写入 body
- 检查返回值:若 read() 返回 4,说明只收到 header;需等待更多数据就绪(配合 Selector 的 OP_READ)后再重试
- 若返回 >4(如 4+12),则对 header 调用 flip() 并读出 length 值;再根据该值调整 body 的 limit(body.limit(length)),最后 flip body 用于读取有效负载
- 若 body Buffer 不够大(例如 length=2000,但只分配了1024),read() 不会越界,只会填满已分配空间;此时需额外逻辑(如扩容 buffer 或分多次读)
用 Gather 构造含变长内容的响应
Gather 天然适合拼接动态内容,因为它只写每个 Buffer 中 position 到 limit 之间的有效数据,不关心容量是否富余。
- 准备多个 Buffer:statusBuf(写入 "HTTP/1.1 200 OK\r\n"),headersBuf(写入动态 header 行),bodyBuf(写入 JSON 或二进制流,写完后 flip)
- 确保每个 Buffer 已完成写入并调用 flip() —— 此时 position=0,limit=实际写入字节数
- 调用 channel.write(new ByteBuffer[]{statusBuf, headersBuf, bodyBuf}),NIO 按序写出各 Buffer 的 limit - position 字节
- 若 write() 返回值小于总期望字节数(如网络缓冲区满),未写完的 Buffer 保持当前 position/limit,下次可继续 write,无需重新 flip
必须注意的底层约束
这些操作不是“万能粘合剂”,忽略限制会导致行为异常或性能倒退。
- 通道类型限制:仅 FileChannel 和非阻塞 SocketChannel / ServerSocketChannel 支持;DatagramChannel 不支持 Scatter/Gather
- Buffer 类型需一致:全部用 direct buffer(推荐),避免 heap buffer 混用引发隐式拷贝或 UnsupportedOperationException
- 顺序即布局:Scatter 按数组索引从左到右填满,Gather 按索引从左到右写出 —— 调换 header/body 顺序会导致协议解析失败
- 不保证原子完成:一次 read/write 可能只处理部分 Buffer;务必检查返回值,并在非阻塞模式下结合 Selector 循环处理
替代思路:当纯 Scatter 不够用时
如果消息头本身也变长(如 HTTP 的 chunked 编码或 TLV 结构),Scatter 的“填满才换 Buffer”机制就不适用。此时建议:
- 先用小 Buffer(如 128B)读一段原始字节到 heap buffer,解析出真实头长和 body 长
- 再分配精准 size 的 body Buffer,用普通 single-buffer read 继续读取
- 或全程使用 gather 写、scatter 读仅用于固定结构部分(如 TLS record header),其余交由业务逻辑流式解析

















