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

Java NIO 核心三大組件詳解:Channel、Buffer、Selector

     編輯:Java 相關知識

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 模擬「管道」,數據從 Channel 讀入 Buffer,或從 Buffer 寫入 Channel。所有 Channel 都是雙向的(除單向文件通道),支持阻塞與非阻塞模式。

主要實現類

核心操作

// 1. 獲取通道
FileChannel fc = new FileInputStream("data.txt").getChannel();
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false); // 切換非阻塞模式

// 2. 讀寫 (需配合 Buffer)
ByteBuffer buf = ByteBuffer.allocate(1024);
int read = fc.read(buf);        // 讀入 Buffer,返回字節數,-1 表示 EOF
buf.flip();                     // 切換讀模式
fc.write(buf);                  // 寫入 Channel

// 3. 文件專用高級特性
fc.transferTo(0, fc.size(), targetChannel); // 零拷貝發送文件
fc.lock(position, size, shared);            // 文件鎖(進程級)
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, size); // 內存映射

三、Buffer — 緩衝區

Buffer 是一塊固定大小的連續內存塊,維護四個核心屬性:

  • capacity:容量,不可變,分配時確定。
  • limit:寫模式下 = capacity;讀模式下 = 已寫入數據量。
  • position:下一個讀寫位置索引。
  • mark:標記位置,reset() 可回退。

狀態轉換圖


寫模式: position=0, limit=capacity
    ↓ 寫入數據 (put)
寫模式: position=n, limit=capacity
    ↓ flip()
讀模式: position=0, limit=n
    ↓ 讀取數據 (get)
讀模式: position=n, limit=n
    ↓ clear() / compact()
寫模式: position=0/剩餘數據, limit=capacity

常用子類型

  • ByteBuffer — 最通用,支持直接內存 allocateDirect(),JNI 零拷貝首選。
  • CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer — 視圖緩衝區,ByteBuffer.asXxxBuffer() 獲取。
  • MappedByteBuffer — 文件內存映射,FileChannel.map() 產生。

關鍵 API 速查

Channel 類型對應場景關鍵特性
FileChannel文件讀寫、內存映射、文件鎖、傳輸只能阻塞模式;支持 transferTo/From 零拷貝
SocketChannelTCP 客戶端連接可非阻塞;connect/finishConnect 異步建連
ServerSocketChannelTCP 服務端監聽可非阻塞;accept() 返回新 SocketChannel
DatagramChannelUDP 數據報收發可非阻塞;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 — 多路複用器

Selector 允許單線程監聽多個 Channel 的就緒事件(連接就緒、讀就緒、寫就緒),實現 Reactor 模式核心。

工作流程

  1. Channel 配置非阻塞:channel.configureBlocking(false)
  2. 向 Selector 註冊關注事件:SelectionKey key = channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE)
  3. 事件循環:while (selector.select() > 0) { ... }
  4. 處理就緒集合: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。

SelectionKey 關鍵操作


// 獲取附屬對象(通常綁定 Handler/Session 上下文)
MyHandler handler = (MyHandler) key.attachment();

// 取消關注
key.cancel(); // 或 key.interestOps(0)

// 動態修改關注事件(線程安全)
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

// 喚醒阻塞中的 select()
selector.wakeup();

五、三大組件協作完整示例:簡易 Echo Server

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); // 切回讀
                    }
                }
            }
        }
    }
}

六、避坑指南與最佳實踐

  1. Buffer 重用:循環中使用 clear()/compact() 而非重新 allocate,減少 GC。
  2. 直接內存:網絡讀寫優先 ByteBuffer.allocateDirect(),避免堆內存↔直接內存拷貝;注意 -XX:MaxDirectMemorySize 限制。
  3. 空輪詢防護:Windows 下 Selector 偶發空輪詢(CPU 100%),需檢測 selectedKeys.isEmpty() 並 selector.select(1) 或重建 Selector。
  4. 半包/粘包:NIO 無消息邊界,應用層需自定義協議(長度首部、分隔符、定長)配合 ByteBuffer 組裝完整消息。
  5. 關閉資源:channel.close() 會自動取消註冊;Selector 需 selector.close() 釋放底層文件描述符。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved