傳統 BIO(Blocking IO)一請求一線程,高併發下線程上下文切換開銷巨大。NIO(New IO / Non-blocking IO)引入 Channel、Buffer、Selector 三大核心,實現 單線程管理多 Channel,成為 Netty、Kafka、RocketMQ 等高性能中間件的基石。
| Channel 實現類 | 對應場景 | 底層協議 |
|---|---|---|
FileChannel | 本地文件讀寫 | 文件系統 |
SocketChannel | TCP 客戶端連接 | TCP |
ServerSocketChannel | TCP 服務端監聽 | TCP |
DatagramChannel | UDP 通信 | UDP |
InputStream 只讀,OutputStream 只寫read() 也可 write()try (FileOutputStream fos = new FileOutputStream("data.txt");
FileChannel channel = fos.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Java NIO 實戰".getBytes(StandardCharsets.UTF_8));
buffer.flip(); // 切換讀模式:limit=position, position=0
channel.write(buffer); // 寫入通道
} // try-with-resources 自動關閉
關鍵點: flip() 將 Buffer 從「寫模式」切換為「讀模式」,否則 channel.write() 會以為無數據可讀而寫入 0 字節。
capacity 容量:固定,分配時確定,不可變 limit 邊界:讀模式下表示「可讀上限」,寫模式下等於 capacity position 位置:下一個要讀/寫的索引,初始 0 mark 標記:調用 mark() 記住當前 position,reset() 恢復
分配 allocate() → 寫模式 (position=0, limit=capacity)
→ put() 寫入數據 (position 推進)
→ flip() 切換讀模式 (limit=position, position=0)
→ get() 讀取數據 (position 推進)
→ clear() 重置寫模式 (position=0, limit=capacity) — 數據未清除,僅索引歸零
→ compact() 未讀數據前移 + 切換寫模式 — 適用於「讀一些、寫一些」流式場景
ByteBuffer — 網絡通信首選,支持直接內存 allocateDirect()CharBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、ShortBuffer — 文件結構化讀寫Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false); // 必須非阻塞
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int ready = selector.select(); // 阻塞直到有就緒事件
if (ready == 0) continue;
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove(); // 必須手動移除,防止重複處理
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel client = ssc.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = client.read(buffer);
if (len > 0) {
buffer.flip();
System.out.println("收到: " + StandardCharsets.UTF_8.decode(buffer));
} else if (len == -1) {
client.close(); // 客戶端正常關閉
}
}
}
}
| 常量 | 含義 | 適用 Channel |
|---|---|---|
OP_ACCEPT | 新連接就緒 | ServerSocketChannel |
OP_CONNECT | 連接完成 | SocketChannel |
OP_READ | 可讀 | SocketChannel, DatagramChannel, FileChannel |
OP_WRITE | 可寫 | SocketChannel, DatagramChannel, FileChannel |
| 模型 | 線程模型 | 適用場景 | 代表框架 |
|---|---|---|---|
| BIO | 一請求一線程 (阻塞) | 低併發、簡單業務 | Tomcat < 8.5 默認 |
| NIO | 單線程輪詢多 Channel (非阻塞) | 高併發、長連接、即時通訊 | Netty, Undertow, Jetty |
| AIO (NIO.2) | 回調/CompletionHandler (異步) | 超高併發、大文件傳輸 | 少見,Java 7+ 支持 |
flip() 再 channel.write(),讀取後必須 clear() 或 compact() 再 channel.read()。channel.configureBlocking(false) 否則 register() 拋 IllegalBlockingModeException。selector.wakeup() 避免 select() 永久阻塞。ByteBuffer.allocateDirect() 分配堆外內存,依賴 GC 回收,高頻場景建議池化或用 Cleaner 顯式釋放。SelectionKey 取消後需 key.channel().close() 徹底釋放資源。Channel 提供「路」,Buffer 裝「貨」,Selector 做「調度」—— 三者協同構建了 Java 高性能網絡編程的基石。徹底理解 Buffer 狀態機與 Selector 事件循環,是讀懂 Netty 源碼、優化 RPC 框架、排查「假死」連接的前提。建議動手實現一個迷你 HTTP 服務器,將三大件串聯起來,方得始終終於手。