Java NIO的Channel是双向非阻塞的,必须配合Buffer使用,需正确调用flip()、clear()等方法管理position/limit/capacity;FileChannel支持随机定位,SocketChannel不支持;Selector要求Channel必须非阻塞,且close()与wakeup()存在线程安全依赖。

Channel 不是 InputStream,别直接当成流来读
Java NIO 的 Channel 是双向的、非阻塞的、必须配合 Buffer 使用——它不像 InputStream 那样能逐字节读,也不自动管理位置。常见错误是调用 channel.read(buffer) 后立刻处理 buffer 内容,却忘了调用 buffer.flip() 切换读写模式。
- 每次
read()或write()前,确保Buffer处于正确状态:flip()用于读取写入的数据,clear()或compact()用于为下一次写入准备 -
FileChannel支持position()和size(),但SocketChannel不支持随机定位,调用会抛NonReadableChannelException - 阻塞模式下
read()可能返回 0(对端未关闭但暂无数据),非阻塞模式下更常见;不能把返回 0 当成 EOF,得看是否触发OP_READ且read()返回 -1
Buffer 的 limit/position/capacity 容易绕晕,靠 print 调试不靠谱
三个字段关系固定:0 ≤ position ≤ limit ≤ capacity。出问题时不是值错了,而是没理解「谁在动谁」。比如 buffer.put("abc".getBytes()) 后 position == 3,此时若直接 channel.write(buffer) 会写 3 字节;但如果之前调过 flip(),limit 就被设为原来的 position,而 position 被重置为 0——这才是读模式的起点。
- 调试时别只打
buffer.toString()(它只返回类名+hash),改用Arrays.toString(buffer.array())+buffer.position()+buffer.limit() -
ByteBuffer.allocateDirect()分配堆外内存,避免拷贝,但创建/销毁成本高;普通allocate()在堆内,GC 管理,小数据量更稳 - 不要复用
Buffer实例跨线程,它不是线程安全的;也不要假设wrap(byte[])后的 buffer 永远和原数组同步——如果原数组被改写,buffer 内容可能变,反之亦然
Selector 注册前必须把 Channel 设为非阻塞,否则 register() 直接抛异常
Selector 只接受非阻塞 Channel。常见错误是先 open SocketChannel,不做 configureBlocking(false) 就去 register(),结果报 IllegalBlockingModeException。而且,一旦注册进 Selector,就不能再切回阻塞模式。
-
ServerSocketChannel和SocketChannel都要显式调configureBlocking(false),FileChannel不支持注册到Selector,别试 -
select()是阻塞的,但可设超时;selectNow()立即返回,适合轮询场景;注意selectedKeys()返回的是引用,遍历完必须keyIterator.remove(),否则下次 select 还会返回它 - 一个
Channel只能注册到一个Selector,但可以同时监听多个 ops,比如OP_READ | OP_WRITE;不过OP_WRITE几乎总是就绪,滥用会导致 busy loop
Selector 的 wakeup() 和 close() 有隐含顺序依赖
Selector.close() 是线程安全的,但调用后所有阻塞在 select() 的线程会被唤醒,并抛 ClosedSelectorException。如果另一个线程正执行 wakeup(),而此时 close 已开始,wakeup 可能失效或引发异常。
立即学习“Java免费学习笔记(深入)”;
- 多线程环境下,务必保证
close()发生在所有select()循环退出之后;建议用 volatile boolean 标记 shutdown 状态,在循环里检查 -
wakeup()不会中断正在执行的 IO 操作,只影响下一次select()调用;如果 select 正在阻塞,它会让其立即返回 0 - 别在
selectedKeys()遍历过程中调close()—— 这会导致 ConcurrentModificationException;先清空 keys,再 close
真正难的不是记住每个 API 的签名,而是当 read() 返回 0、select() 返回 1 却没看到预期 key、或者 buffer 里突然出现乱码时,你第一反应是查 position 还是抓包。这些细节不会写在 Javadoc 里,但每次卡住,八成都栽在这几个地方。










