java nio selector处理就绪事件需按位判断readyops与interestops、主动清理key、分离读写逻辑:读就绪须检查read()返回值并清空缓冲区,写就绪仅临时注册且及时取消,遍历selectedkeys必须用iterator并调用remove()。

Java NIO 中 Selector 处理 SelectionKey 就绪事件,核心在于“按位判断 + 主动清理 + 事件分离”,不是简单调用 isReadable() 或 isWritable() 就完事。关键逻辑藏在 readyOps() 与 interestOps() 的按位与运算里,且必须配合手动移除 key 和缓冲区状态管理。
读就绪(OP_READ)要检查返回值,不能只看状态
当 (key.readyOps() & SelectionKey.OP_READ) != 0 成立时,才表示真正可读。但此时仍需谨慎操作:
- 调用
channel.read(buffer)后,必须检查返回值:-1 表示对端关闭,需取消 key 并关闭 channel;0 表示暂无新数据(非错误);>0 才是实际读到的字节数 - 读之前务必调用
buffer.clear()(或compact(),若 buffer 中还有未消费数据) - 读到 -1 后,应立即取消 key、关闭 channel、释放资源,否则可能引发空轮询或连接泄漏
写就绪(OP_WRITE)不能长期注册,只用于缓冲区满时的补发
OP_WRITE 大多数时候始终就绪(只要内核发送缓冲区有空间),盲目注册会导致 CPU 空转。正确做法是:
- 默认不注册
OP_WRITE;仅当channel.write(buffer)返回 0(即 buffer 还有剩余但写不出去)时,才临时注册:key.interestOps(key.interestOps() | SelectionKey.OP_WRITE) - 收到写就绪通知后,继续尝试写出剩余数据;一旦
buffer.hasRemaining() == false,立刻取消写关注:key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE) - 同样要检查
write()返回值:0 表示还需等待,>0 表示成功写出字节数
遍历 selectedKeys 时必须用迭代器并 remove
selector.selectedKeys() 返回的是一个可变集合,key 不会自动移除。如果跳过 iterator.remove():
- 同一个 key 可能在下一轮 select 中再次出现,导致重复处理
- 若处理中发生异常(如 IOException),未 remove 的 key 会持续触发,拖慢整个循环
- 必须用
Iterator<selectionkey></selectionkey>遍历,并在处理完每个 key 后立即调用it.remove()
就绪状态是快照,一个 key 可能同时读写就绪
每次 select() 返回的就绪集是内核在那一刻的“事件快照”,因此:
- 单个
SelectionKey在一次遍历中可能同时满足OP_READ和OP_WRITE(例如半双工通信或 echo 场景),应分别独立判断和处理 - 处理顺序无强制要求,但建议先读后写,避免写操作阻塞读响应
- 切勿在处理过程中调用
key.cancel()后再访问key.channel(),已取消的 key 渠道不可用
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











