Java NIO(New IO / Non-blocking IO)以 Channel、Buffer、Selector 三大核心組件構建高性能網絡與文件 IO 模型。不同於傳統 Stream 的阻塞流式處理,NIO 採用「通道-緩衝區」雙向傳輸與「多路複用器」事件驅動機制,是 Netty、Kafka、RocketMQ 等高性能中間件的基石。本文系統梳理三大組件體系、核心 API 及協作流程。
+----------------+ read/write +---------+
| Channel | <-----------------> | Buffer |
| (File/Socket/ | 雙向流動 | (Heap/ |
| Datagram/ | | Direct) |
| ServerSocket) | +---------+
+----------------+ ^
^ |
| register |
| |
+------+------+ |
| Selector | 多路複用器:單線程監聽多 Channel 事件
| (Select/ | keySet() -> SelectionKey -> Channel/Buffer
| Wakeup) |
+-------------+
Channel 模擬「管道」,數據從 Channel 讀入 Buffer,或從 Buffer 寫入 Channel。所有 Channel 都是雙向的(除單向文件通道),支持阻塞與非阻塞模式。
| Channel 類型 | 對應場景 | 關鍵特性 |
|---|---|---|
FileChannel | 文件讀寫、內存映射、文件鎖、傳輸 | 只能阻塞模式;支持 transferTo/From 零拷貝 |
SocketChannel | TCP 客戶端連接 | 可非阻塞;connect/finishConnect 異步建連 |
ServerSocketChannel | TCP 服務端監聽 | 可非阻塞;accept() 返回新 SocketChannel |
DatagramChannel | UDP 數據報收發 | 可非阻塞;send/receive 帶 SocketAddress |
| 方法 | 用途 | 典型調用時機 |
|---|---|---|
flip() | 寫→讀:limit=position, position=0 | 寫完數據準備讀取/發送前 |
clear() | 重置寫模式:position=0, limit=capacity | 讀完數據準備下一次寫入前 |
compact() | 未讀數據前移,保留未讀部分 | 非阻塞讀取可能不完整時 |
rewind() | position=0,limit 不變(重讀) | 需要多次讀取同一數據 |
hasRemaining() | position < limit | 讀寫循環判斷條件 |
remaining() | limit - position | 批量操作長度計算 |
Selector 允許單線程監聽多個 Channel 的就緒事件(連接就緒、讀就緒、寫就緒),實現 Reactor 模式核心。
channel.configureBlocking(false)SelectionKey key = channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE)while (selector.select() > 0) { ... }for (SelectionKey key : selector.selectedKeys()) { ... }| 常量 | 含義 | 適用 Channel |
|---|---|---|
OP_CONNECT | 連接完成就緒 | SocketChannel |
OP_ACCEPT | 新連接就緒可接受 | ServerSocketChannel |
OP_READ | 讀就緒(有數據可讀) | SocketChannel, DatagramChannel, FileChannel* |
OP_WRITE | 寫就緒(緩衝區可寫) | SocketChannel, DatagramChannel |
* FileChannel 不支持 Selector,文件 IO 本質阻塞,異步文件 IO 需 JDK 7+ AsynchronousFileChannel。
// 獲取附屬對象(通常綁定 Handler/Session 上下文)
MyHandler handler = (MyHandler) key.attachment();
// 取消關注
key.cancel(); // 或 key.interestOps(0)
// 動態修改關注事件(線程安全)
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// 喚醒阻塞中的 select()
selector.wakeup();
public class NioEchoServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(8080));
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞等待事件
for (SelectionKey key : selector.selectedKeys()) {
selector.selectedKeys().remove(key); // 必須手動移除
if (!key.isValid()) continue;
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ, new ByteBuffer(1024));
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
int read = client.read(buf);
if (read == -1) { client.close(); continue; }
buf.flip();
key.interestOps(SelectionKey.OP_WRITE); // 切換關注寫
} else if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
client.write(buf);
if (!buf.hasRemaining()) {
buf.clear();
key.interestOps(SelectionKey.OP_READ); // 切回讀
}
}
}
}
}
}
clear()/compact() 而非重新 allocate,減少 GC。ByteBuffer.allocateDirect(),避免堆內存↔直接內存拷貝;注意 -XX:MaxDirectMemorySize 限制。selectedKeys.isEmpty() 並 selector.select(1) 或重建 Selector。ByteBuffer 組裝完整消息。channel.close() 會自動取消註冊;Selector 需 selector.close() 釋放底層文件描述符。