Java NIO的核心价值在于解决高并发、大文件、零拷贝等场景下传统阻塞I/O的瓶颈,其本质是面向非阻塞、通道化、缓冲区驱动的底层抽象,而非简单替代IO。

Java NIO 的核心价值不在“替代 IO”,而在解决传统阻塞式 I/O 在高并发、大文件、零拷贝等场景下的瓶颈。 它不是一套“更高级的文件读写工具”,而是一组面向非阻塞、通道化、缓冲区驱动模型的底层抽象。
Buffer 是数据中转站,不是容器
所有 Buffer(如 ByteBuffer、CharBuffer)本质是带状态的数组封装体,关键在于 position、limit、capacity 三者协同。调用 flip() 不是“翻转内容”,而是把 position 归零、limit 设为当前 position,为读出数据做准备;clear() 重置状态但不清空数据;compact() 则保留未读部分并前移,适合部分消费后继续写入的场景。
常见错误:直接对 ByteBuffer.allocate(1024) 调用 get() 报 BufferUnderflowException——因为没先 flip();或重复 put() 导致 BufferOverflowException——忘了检查 hasRemaining()。
- 用
wrap(byte[])包装已有数组时,修改原数组会同步反映在 buffer 中(共享底层数组) - 堆外内存用
allocateDirect(),避免 GC 压力,但分配/释放成本更高,不适合小量短生命周期 buffer - 字符编码转换必须通过
CharsetEncoder/CharsetDecoder,不能直接ByteBuffer和String互转
Channel 是双向数据管道,不是流
FileChannel、SocketChannel、ServerSocketChannel 都继承自 Channel,支持 read() 和 write() 双向操作,且多数可配合 Buffer 实现 scatter/gather(分散写 / 聚集读)。与 InputStream/OutputStream 的单向、字节流语义有根本区别。
立即学习“Java免费学习笔记(深入)”;
典型使用场景:
-
FileChannel.transferTo()和transferFrom()支持零拷贝(Linux 下走sendfile系统调用),绕过 JVM 堆内存,适合大文件复制 -
FileChannel.map()返回MappedByteBuffer,将文件区域映射为内存,适合随机读写超大日志或数据库文件(注意force()才真正刷盘) -
SocketChannel必须在非阻塞模式下才能注册到Selector,否则configureBlocking(false)会抛IllegalBlockingModeException
Selector 是单线程管理多连接的关键
Selector 本身不处理 I/O,只监听多个 Channel 的就绪状态(connect / accept / read / write)。它依赖操作系统提供的 I/O 多路复用机制(epoll/kqueue/select),让一个线程轮询成百上千个连接,避免为每个连接开线程的资源消耗。
容易忽略的细节:
- 注册前必须调用
configureBlocking(false),否则无法注册 -
select()返回的是“就绪 Channel 数量”,不是全部 Channel;需遍历selectedKeys()并手动remove()当前 key,否则下次循环还会触发(key 不自动清除) -
OP_READ就绪不代表一定能读完,可能只读到部分数据,需循环read()直到返回 -1 或 0(非阻塞模式下返回 0 表示暂时无数据)
Path / Files / FileVisitor 是现代文件操作的主力
java.nio.file 包里的 Path、Files、SimpleFileVisitor 才是日常文件操作的推荐方式,比 java.io.File 更安全、更灵活、更可扩展。
例如:
-
Files.walk()替代递归listFiles(),天然支持深度控制和异常处理策略(FileVisitOption.FOLLOW_LINKS可选) -
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING)原子性更强,支持跨文件系统移动(ATOMIC_MOVE) -
Files.createTempDirectory()比File.createTempFile()更明确语义,且默认使用系统临时目录和安全权限 -
Paths.get("a/b/c")返回Path,不是字符串拼接,自动处理路径分隔符和规范化(..、.)
最常被跳过的一步:Files.exists(path) 默认不检查符号链接目标是否存在,要用 Files.exists(path, LinkOption.NOFOLLOW_LINKS) 显式控制。











