首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java NIO 核心三大件深度解析:Channel、Buffer、Selector 從原理到實戰

     編輯:Java 綜合教學

為什麼需要 NIO?

傳統 BIO(Blocking IO)一請求一線程,高併發下線程上下文切換開銷巨大。NIO(New IO / Non-blocking IO)引入 Channel、Buffer、Selector 三大核心,實現 單線程管理多 Channel,成為 Netty、Kafka、RocketMQ 等高性能中間件的基石。

一、Channel:雙向通道,不再是單向流

Channel 實現類對應場景底層協議
FileChannel本地文件讀寫文件系統
SocketChannelTCP 客戶端連接TCP
ServerSocketChannelTCP 服務端監聽TCP
DatagramChannelUDP 通信UDP

關鍵差異:Stream vs Channel

  • Stream:單向,InputStream 只讀,OutputStream 只寫
  • Channel:雙向,同一實例可 read() 也可 write()
  • Channel 必須配合 Buffer 交互數據,無法直接操作字節數組

FileChannel 寫入範例

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 字節。

二、Buffer:數據容器,三大屬性掌控讀寫

capacity  容量:固定,分配時確定,不可變
limit     邊界:讀模式下表示「可讀上限」,寫模式下等於 capacity
position  位置:下一個要讀/寫的索引,初始 0
mark      標記:調用 mark() 記住當前 position,reset() 恢復

Buffer 狀態機

分配 allocate() → 寫模式 (position=0, limit=capacity)
    → put() 寫入數據 (position 推進)
    → flip() 切換讀模式 (limit=position, position=0)
    → get() 讀取數據 (position 推進)
    → clear() 重置寫模式 (position=0, limit=capacity) — 數據未清除,僅索引歸零
    → compact() 未讀數據前移 + 切換寫模式 — 適用於「讀一些、寫一些」流式場景

常用 Buffer 子類

  • ByteBuffer — 網絡通信首選,支持直接內存 allocateDirect()
  • CharBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、ShortBuffer — 文件結構化讀寫

三、Selector:單線程多路復用核心

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();            // 客戶端正常關閉
            }
        }
    }
}

SelectionKey 四大事件類型

常量含義適用 Channel
OP_ACCEPT新連接就緒ServerSocketChannel
OP_CONNECT連接完成SocketChannel
OP_READ可讀SocketChannel, DatagramChannel, FileChannel
OP_WRITE可寫SocketChannel, DatagramChannel, FileChannel

四、BIO vs NIO vs AIO 一覽

模型線程模型適用場景代表框架
BIO一請求一線程 (阻塞)低併發、簡單業務Tomcat < 8.5 默認
NIO單線程輪詢多 Channel (非阻塞)高併發、長連接、即時通訊Netty, Undertow, Jetty
AIO (NIO.2)回調/CompletionHandler (異步)超高併發、大文件傳輸少見,Java 7+ 支持

五、實戰避坑指南

  1. flip() 忘不掉: 寫入 Buffer 後必須 flip() 再 channel.write(),讀取後必須 clear() 或 compact() 再 channel.read()。
  2. 非阻塞模式強制: channel.configureBlocking(false) 否則 register() 拋 IllegalBlockingModeException。
  3. Selector 喚醒: 異步關閉 Channel 時調用 selector.wakeup() 避免 select() 永久阻塞。
  4. 直接內存釋放: ByteBuffer.allocateDirect() 分配堆外內存,依賴 GC 回收,高頻場景建議池化或用 Cleaner 顯式釋放。
  5. SelectionKey 取消後需 key.channel().close() 徹底釋放資源。

結語

Channel 提供「路」,Buffer 裝「貨」,Selector 做「調度」—— 三者協同構建了 Java 高性能網絡編程的基石。徹底理解 Buffer 狀態機與 Selector 事件循環,是讀懂 Netty 源碼、優化 RPC 框架、排查「假死」連接的前提。建議動手實現一個迷你 HTTP 服務器,將三大件串聯起來,方得始終終於手。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved