Java NIO (New I/O) 深度解析
一、核心概念
Java NIO 是 Java 1.4 引入的高性能 I/O 框架,与传统 I/O (java.io) 相比,它提供了:
- 非阻塞 I/O:线程无需等待 I/O 操作完成
- 缓冲区导向:数据先读入缓冲区再处理
- 通道机制:双向数据传输管道
- 选择器:单线程管理多个通道
二、核心组件对比
组件 | 传统 I/O | NIO | 优势 |
---|---|---|---|
数据单位 | 流(Stream) | 缓冲区(Buffer) | 批量处理,减少系统调用 |
传输方式 | 单向 | 通道(Channel)双向 | 全双工通信 |
阻塞模式 | 阻塞式 | 非阻塞/选择器 | 高并发支持 |
线程模型 | 1连接=1线程 | 1线程=多连接 | 资源利用率高 |
三、核心组件详解
1. 缓冲区(Buffer)
内存块容器,关键属性:
public abstract class Buffer {private int capacity; // 最大容量private int position; // 当前读写位置private int limit; // 可操作数据边界private int mark; // 标记位置
}
常用缓冲区类型:
- ByteBuffer(最常用)
- CharBuffer
- IntBuffer
- FloatBuffer
缓冲区操作示例:
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);// 写入数据
buffer.put("Hello".getBytes());// 切换为读模式
buffer.flip();// 读取数据
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println(new String(data)); // 输出: Hello
2. 通道(Channel)
双向数据传输管道,主要实现:
FileChannel
:文件操作SocketChannel
:TCP 客户端ServerSocketChannel
:TCP 服务端DatagramChannel
:UDP 通信
文件复制示例:
try (FileChannel src = new FileInputStream("source.txt").getChannel();FileChannel dest = new FileOutputStream("dest.txt").getChannel()) {// 零拷贝文件传输src.transferTo(0, src.size(), dest);
}
3. 选择器(Selector)
多路复用器,实现单线程管理多个通道:
Selector selector = Selector.open();// 注册通道到选择器
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);// 事件循环
while (true) {int readyChannels = selector.select(); // 阻塞直到有事件if (readyChannels == 0) continue;Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> iter = keys.iterator();while (iter.hasNext()) {SelectionKey key = iter.next();if (key.isReadable()) {// 处理读事件} else if (key.isWritable()) {// 处理写事件} else if (key.isAcceptable()) {// 处理新连接} else if (key.isConnectable()) {// 处理连接完成}iter.remove();}
}
四、NIO 网络编程模型
1. Reactor 模式
2. 工作流程
- 通道注册到选择器
- 选择器监听 I/O 事件
- 事件触发后派发给处理器
- 处理器执行非阻塞操作
五、NIO vs 传统 I/O
特性 | 传统 I/O | NIO |
---|---|---|
阻塞模式 | 阻塞式 | 非阻塞式 |
缓冲区 | 无内置缓冲 | 强制使用缓冲区 |
线程模型 | 1:1(连接:线程) | M:N(多路复用) |
适用场景 | 低连接数 | 高并发连接 |
API复杂度 | 简单 | 复杂 |
数据传输 | 流式 | 块传输 |
六、高级特性
1. 内存映射文件
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();// 映射文件到内存
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size()
);// 直接操作内存
buffer.put(0, (byte) 'A'); // 修改文件内容
2. 分散/聚集 I/O
// 分散读(Scatter)
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {header, body};
channel.read(buffers);// 聚集写(Gather)
channel.write(buffers);
3. 文件锁
FileLock lock = channel.lock(); // 排他锁
try {// 操作受保护文件
} finally {lock.release();
}
七、性能优化技巧
直接缓冲区:减少内存拷贝
ByteBuffer.allocateDirect(1024);
缓冲区复用:避免频繁创建/销毁
private static ThreadLocal<ByteBuffer> bufferCache = ThreadLocal.withInitial(() -> ByteBuffer.allocate(8192));
批量操作:减少系统调用次数
channel.write(bufferArray);
选择器优化:
- 使用
selectNow()
避免阻塞 - 合理设置
interestOps
- 及时移除处理过的 SelectionKey
- 使用
八、典型应用场景
- 高并发服务器:Web服务器、游戏服务器
- 文件传输系统:大文件高效传输
- 消息中间件:Kafka、RocketMQ
- 数据库连接池:高效管理连接
- 实时通信系统:聊天服务器、推送服务
九、注意事项
- 复杂性:NIO API 比传统 I/O 复杂
- 内存管理:直接缓冲区需手动管理
- 连接管理:需处理半关闭状态
- 空轮询问题:JDK 的 select 空转问题
// 解决方案:设置超时 selector.select(100);
十、现代演进
- AIO (Asynchronous I/O):JDK7 引入的真正异步 I/O
- Netty 框架:基于 NIO 的高性能网络框架
- Project Loom:虚拟线程简化并发编程
最佳实践:对于大多数高性能网络应用,推荐使用基于 NIO 的网络框架(如 Netty)而非直接使用 NIO API,可显著降低开发复杂度并提高性能。