Scatter与Gather是Java NIO中Channel通过ByteBuffer数组实现的高效I/O机制:Scatter一次read按序填充多个Buffer(如4字节header+body),Gather一次write按序写出多个已flip的Buffer内容,仅支持FileChannel和非阻塞SocketChannel,需类型一致、顺序敏感且必须检查返回值。
![scatter 与 gather:解析 nio 通道如何通过 read(buffer[]) 实现离散数据的批量聚合读写](https://img.php.cn/upload/article/001/242/473/177969530119885.jpeg)
Scatter 与 Gather 是 Java NIO 中 Channel 对结构化数据进行高效 I/O 的核心机制,本质是用一个数组替代单个 Buffer,让底层自动按序填充或拼接,不依赖手动拷贝,也不需要提前拼成大字节数组。它们不是“批量读写”的泛称,而是特指 read(ByteBuffer[]) 和 write(ByteBuffer[]) 这两个接口行为。
Scatter(分散读):固定头 + 可变体的解析利器
Scatter 读要求数据按 Buffer 数组顺序依次填满——前一个 Buffer 必须写满,才向下一个写。这决定了它只适合头部长度严格固定的协议场景。
- 典型用法:分配
header = ByteBuffer.allocate(4)(存消息长度字段),body = ByteBuffer.allocate(8192)(存实际负载) - 调用
channel.read(new ByteBuffer[]{header, body})后,NIO 先填满 header 的 4 字节,再把剩余字节写入 body - 若网络只返回 2 字节,
read()返回 2,两个 Buffer 都未 flip,需等待就绪后重试(常配合 Selector 使用) - 读完必须对每个 Buffer 单独调用
flip(),才能正确读取 header 中的长度值、body 中的有效内容
Gather(聚集写):灵活拼接响应,避免内存复制
Gather 写不强制填满每个 Buffer,只写 position 到 limit 之间的有效数据,因此天然支持动态内容。
- 常见组合:状态码 Buffer(已写入 "200 OK\r\n" 并 flip)、JSON Buffer(内容写好并 flip)、换行 Buffer("\r\n")
- 调用
channel.write(new ByteBuffer[]{statusBuf, jsonBuf, crlfBuf}),NIO 按序写出三段,等效于拼接后发送 - 返回值是实际写出总字节数;若网络缓冲区满,未写完的 Buffer 保持当前 position/limit,下次可继续 write
- 无需手动合并、无额外 byte[] 创建,减少 GC 压力,特别适合高频小响应场景
关键限制与实操要点
Scatter/Gather 不是万能语法糖,有明确约束:
- 仅支持
FileChannel、非阻塞SocketChannel和ServerSocketChannel;DatagramChannel不支持 - 所有 Buffer 类型必须一致(建议全用
allocateDirect(),避免堆内拷贝) - 数组顺序即数据布局顺序:Scatter 从左到右填,Gather 从左到右发
- 必须检查
read()/write()返回值,尤其在非阻塞模式下,部分完成是常态
为什么不能混用或随意调整 Buffer 容量
Scatter 要求“填满才换”,意味着 Buffer 容量就是协议字段的硬边界。若 header 设为 128 字节但实际只有 100 字节,剩余 28 字节会被零填充或导致后续 body 偏移错乱;Gather 虽不填满,但若在 write 过程中修改某个 Buffer 的 capacity 或重新分配 backing array,行为未定义——JVM 无法保证底层地址连续性,可能引发数据截断或越界。

















