Java NIO通道写入需循环处理:检查write()返回值,0时注册OP_WRITE等待,正数n则compact()或移动position,-1时清理资源;用compact()保留未写数据,配合Selector监听可写事件重试。

Java NIO 中通道写入(channel.write())不保证一次性写完全部字节,返回值是实际写入的字节数,可能小于缓冲区中剩余数据量。正确处理未完全写入的残余数据,核心在于**循环写入 + 状态管理 + 关注缓冲区位置与限制**。
检查 write() 返回值并手动推进缓冲区
每次调用 SocketChannel.write() 或 FileChannel.write() 后,必须检查返回值:
- 返回值为 0:内核发送缓冲区已满,需等待可写事件(如注册
SelectionKey.OP_WRITE)再重试; - 返回值为 正数 n:成功写入 n 字节,需调用
buffer.compact()(适用于堆外/直接缓冲区的连续写场景)或buffer.position(buffer.position() + n)手动移动位置; - 返回值为 -1:对已关闭连接写入,应清理资源;
使用 compact() 而非 clear() 维持待写数据
若采用单个 ByteBuffer 多次写入(如响应拼装后统一发送),切勿在写入后直接 clear() —— 这会丢弃未写完的残留内容。正确做法是:
- 写入后调用
buffer.compact():将未写完的数据移到缓冲区开头,更新 position 和 limit,为下次 write 做好准备; - 之后可继续向 buffer 写入新数据(
put()),或再次调用write()尝试发送剩余部分; - 当
buffer.hasRemaining() == false时,说明本次数据已全部发出。
结合 Selector 实现非阻塞重试(关键用于网络通道)
对于非阻塞 SocketChannel,不能轮询或 sleep 等待,而应借助 Selector 监听可写就绪:
立即学习“Java免费学习笔记(深入)”;
- 首次 write 返回值 OP_WRITE 到 selector;
- 在 select 循环中捕获
OP_WRITE事件 → 再次调用 write(); - 写完后务必取消
OP_WRITE注册(除非还有后续数据要发),避免空转触发; - 建议将待发送的 ByteBuffer 封装进自定义连接对象(如
Connection),便于跟踪写状态和持有缓冲区引用。
更稳妥的做法:用独立输出缓冲区 + 写状态机
生产环境推荐为每个连接维护一个专属的写缓冲区(如 ConcurrentLinkedQueue<ByteBuffer> 或单个可扩容的 CompositeByteBuf 类似结构),配合简单状态机:
- 应用层调用 “send(data)” → 将数据包装为
ByteBuffer入队; - 写就绪时,从队首取 buffer,执行 write();
- 若未写完,compact 后放回队首(或保留引用、只更新 position);
- 若写完,移除该 buffer,继续处理下一个;
- 队列为空时,取消
OP_WRITE。


















