java通过filechannel和socketchannel的write(bytebuffer[])与read(bytebuffer[])支持scatter-gather i/o,利用os的readv/writev系统调用提升吞吐量;需全用direct buffer,避免复制,返回值为实际字节数。

Java 中没有直接叫“Scatter-Gather”的原生 API,但 java.nio.channels.Channel(尤其是 FileChannel 和 SocketChannel)支持类似 scatter-gather 的 IO 操作,核心是通过 ByteBuffer 数组 一次性读写多个缓冲区,底层利用操作系统级的 readv/writev 系统调用,避免多次内核态/用户态切换,提升吞吐量。
使用 ByteBuffer 数组进行 gather 写入(一次写多个 buffer)
调用 channel.write(ByteBuffer[]),数据按数组顺序从各 buffer 中“gather”后连续写入通道(如文件或 socket)。适合将消息头、正文、校验码等逻辑上分离但物理上需连续发送的数据,免去手动拼接 byte[] 或额外 copy。
- buffer 数组中每个 buffer 的
position到limit区间会被写入;写完后各 buffer 的position自动前移已写长度 - 返回值是实际写入的总字节数(可能小于所有 buffer 剩余容量之和,尤其在非阻塞模式下)
- 示例:发送 HTTP 响应时,用一个 buffer 存状态行,一个存 headers,一个存 body,一次
channel.write(buffers)
使用 ByteBuffer 数组进行 scatter 读取(一次读到多个 buffer)
调用 channel.read(ByteBuffer[]),数据按数组顺序依次填满各 buffer(先填满第一个,再填第二个……),适用于解析固定格式协议(如前 4 字节是长度字段,后面是 payload)。
- 每个 buffer 的
limit决定最多能读多少;position是起始写入位置 - 若数据不足,靠前的 buffer 可能被填满,后面的保持不变;返回值为实际读到的总字节数
- 注意:socket channel 在非阻塞模式下常返回 0,需结合
SelectionKey判断是否就绪
关键细节与注意事项
scatter-gather 不是魔法,它依赖底层 OS 支持,且行为受 channel 类型和模式影响:
-
FileChannel 总是阻塞的,
read/write(ByteBuffer[])会尽量完成全部(除非到达 EOF 或磁盘满),适合批量文件处理 - SocketChannel 在阻塞模式下也会尽力读/写完数组全部内容;非阻塞模式下必须检查返回值,并自行处理部分完成情况
- 所有 buffer 必须是 direct buffer(堆外)才能获得最佳性能,否则 JDK 可能内部复制 —— 尤其对大数组或高频操作,建议用
ByteBuffer.allocateDirect() - 不能混用 heap buffer 和 direct buffer 在同一数组中调用(虽不报错,但 heap buffer 会被临时复制,失去 scatter-gather 意义)
简单代码片段示意
以写入为例(省略异常处理和资源关闭):
FileChannel out = FileChannel.open(Paths.get("out.bin"), WRITE, CREATE);
ByteBuffer header = ByteBuffer.allocateDirect(8).putLong(0x123456789ABCDEFL).flip();
ByteBuffer body = ByteBuffer.allocateDirect(1024).put("Hello World".getBytes()).flip();
ByteBuffer[] buffers = { header, body };
out.write(buffers); // 一次写入 header + body,共 8+11=19 字节
out.close();
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











