Java NIO(New I/O,非阻塞 I/O)在 JDK 1.4 引入,AIO(Asynchronous I/O)在 JDK 1.7 引入。理解这些 I/O 模型是掌握 Netty 等高性能框架的基础。
一、I/O 模型对比
1.1 五种 I/O 模型
| 模型 | 阻塞点 | Java 支持 | 特点 |
|---|---|---|---|
| 阻塞 I/O(BIO) | 全程阻塞 | JDK 1.0 | 一个连接一个线程 |
| 非阻塞 I/O(NIO) | 无 | JDK 1.4 | 多路复用,单线程处理多连接 |
| I/O 多路复用 | select/poll/epoll | JDK 1.4 | Selector 机制 |
| 信号驱动 I/O | 无 | 无原生支持 | Linux 信号机制 |
| 异步 I/O(AIO) | 完全无阻塞 | JDK 1.7 | 内核完成 I/O 后回调 |
1.2 形象比喻
阻塞 I/O(BIO):
你去餐厅,点餐后站在柜台等,直到餐做好才离开(阻塞)
非阻塞 I/O(NIO):
你去餐厅,点餐后拿个号牌,每隔几分钟去问一次好了没(轮询)
I/O 多路复用:
你去餐厅,点餐后坐在座位上,服务员做好后叫你的号牌(事件通知)
异步 I/O(AIO):
你打电话点餐,商家做好后送到你家(完全异步, callback)
二、NIO 三大核心组件
2.1 Channel(通道)
// Channel 是双向的,区别于 Stream 的单向
// 主要实现:
// 文件通道
FileChannel fileChannel = new RandomAccessFile("data.txt", "rw").getChannel();
// 网络通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
SocketChannel socketChannel = SocketChannel.open();
// UDP 通道
DatagramChannel datagramChannel = DatagramChannel.open();
2.2 Buffer(缓冲区)
// NIO 数据读写必须通过 Buffer
// 创建 Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024); // 堆内存
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024); // 直接内存
// Buffer 状态属性
// position: 当前读写位置
// limit: 可以读写的边界
// capacity: 最大容量
// 写入数据(写模式)
buffer.put("Hello".getBytes());
buffer.putInt(123);
// 切换为读模式
buffer.flip();
// flip():limit = position, position = 0
// 读取数据
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println(new String(data));
// 清空,重新写入
buffer.clear();
// clear():position = 0, limit = capacity
// 或者 compact():将未读数据移到头部
buffer.compact();
2.3 Selector(多路复用器)
// Selector 是 NIO 的核心,允许单线程监控多个 Channel
Selector selector = Selector.open();
// 注册 Channel 到 Selector
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false); // 必须非阻塞
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
// 事件循环
while (true) {
// 阻塞等待就绪事件(有超时版本)
int readyCount = selector.select();
if (readyCount == 0) continue;
// 获取就绪的 SelectionKey
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove(); // 必须移除,否则重复处理
if (key.isAcceptable()) {
// 有新的连接请求
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("Client connected: " + client.getRemoteAddress());
}
if (key.isReadable()) {
// 有数据可读
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = client.read(buffer);
if (read == -1) {
// 连接关闭
key.cancel();
client.close();
} else if (read > 0) {
buffer.flip();
// 处理数据...
System.out.println("Received: " + new String(buffer.array(), 0, read));
// 注册写事件(响应客户端)
key.interestOps(SelectionKey.OP_WRITE);
}
}
if (key.isWritable()) {
// 可以写入数据
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer response = ByteBuffer.wrap("OK\n".getBytes());
client.write(response);
// 写完后改回读事件
key.interestOps(SelectionKey.OP_READ);
}
}
}
三、完整 NIO Server 示例
public class NioEchoServer {
private Selector selector;
private ServerSocketChannel serverChannel;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
public void start(int port) throws IOException {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(port));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Server started on port " + port);
while (true) {
selector.select();
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isAcceptable()) {
handleAccept(key);
} else if (key.isReadable()) {
handleRead(key);
} else if (key.isWritable()) {
handleWrite(key);
}
}
}
}
private void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
System.out.println("Accepted connection from " + client.getRemoteAddress());
}
private void handleRead(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
buffer.clear();
int read = client.read(buffer);
if (read == -1) {
System.out.println("Client disconnected: " + client.getRemoteAddress());
key.cancel();
client.close();
return;
}
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data);
System.out.println("Received from " + client.getRemoteAddress() + ": " + message.trim());
// 准备响应
key.attach("Echo: " + message);
key.interestOps(SelectionKey.OP_WRITE);
}
private void handleWrite(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
String response = (String) key.attachment();
ByteBuffer buf = ByteBuffer.wrap(response.getBytes());
client.write(buf);
// 写完后继续监听读取
key.interestOps(SelectionKey.OP_READ);
key.attach(null);
}
public static void main(String[] args) throws IOException {
new NioEchoServer().start(8080);
}
}
四、AIO(NIO.2)
4.1 异步通道
public class AioEchoServer {
public static void main(String[] args) throws IOException, InterruptedException {
AsynchronousServerSocketChannel server =
AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
System.out.println("AIO Server started on port 8080");
// 异步接受连接(回调方式)
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel client, Void attachment) {
// 继续接受下一个连接
server.accept(null, this);
// 处理当前连接
handleClient(client);
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
// 主线程阻塞,等待异步操作
Thread.sleep(Long.MAX_VALUE);
}
private static void handleClient(AsynchronousSocketChannel client) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 异步读取
client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
if (result == -1) {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data);
System.out.println("Received: " + message.trim());
// 异步写入响应
ByteBuffer response = ByteBuffer.wrap(("Echo: " + message).getBytes());
client.write(response, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer result, Void attachment) {
// 继续读取
ByteBuffer newBuffer = ByteBuffer.allocate(1024);
client.read(newBuffer, newBuffer, this);
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, ByteBuffer buffer) {
exc.printStackTrace();
}
});
}
}
4.2 Future 方式
// Future 方式(阻塞获取结果,不推荐)
Future<AsynchronousSocketChannel> future = server.accept();
AsynchronousSocketChannel client = future.get(); // 阻塞
// 更好的方式:使用 CompletionHandler(回调)
// 或使用 CompletableFuture 包装
五、NIO vs AIO 对比
| 特性 | NIO | AIO |
|---|---|---|
| 模型 | 多路复用(Reactor) | 真正异步(Proactor) |
| 阻塞 | 非阻塞(需轮询 Selector) | 完全非阻塞 |
| 线程 | 应用处理 I/O | 内核处理 I/O,应用接收回调 |
| 实现复杂度 | 较复杂 | 较简单 |
| Linux 实现 | epoll | io_uring(新)/ aio(旧,有限制) |
| 成熟度 | 高(Netty 基于此) | 中 |
| 适用场景 | 高并发网络服务 | 文件 I/O 为主 |
六、Channel 类型速查
// 文件通道(NIO)
FileChannel fc = FileChannel.open(Path.of("file.txt"), StandardOpenOption.READ);
MappedByteBuffer mapped = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); // 内存映射
// Socket 通道
SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 80));
sc.configureBlocking(false);
// ServerSocket 通道
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8080));
// 文件传输(零拷贝)
FileChannel src = new FileInputStream("src.txt").getChannel();
FileChannel dst = new FileOutputStream("dst.txt").getChannel();
src.transferTo(0, src.size(), dst); // 零拷贝传输
七、总结
| 组件 | 职责 | 关键 API |
|---|---|---|
| Channel | I/O 通道,双向传输 | open(), configureBlocking(), register() |
| Buffer | 数据容器 | allocate(), flip(), clear(), compact() |
| Selector | 多路复用器 | open(), select(), selectedKeys() |
| SelectionKey | 注册关系与事件 | interestOps(), readyOps(), attach() |
NIO 是 Java 高性能网络编程的基石。Selector 的多路复用机制让单个线程可以高效管理数千个连接,Buffer 的引入减少了数据拷贝,而 Channel 的双向特性简化了 I/O 操作。Netty 正是在这些基础之上,通过更高效的内存管理和线程模型,实现了百万级并发。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。