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

Java NIO 核心原理與實戰:從阻塞 I/O 到多路複用的效能躍升

     編輯:Java 綜合教學

核心概念對照

  • BIO (Blocking I/O):阻塞式 I/O,無論磁碟或網路,寫入 OutputStream/讀取 InputStream 皆可能阻塞,線程失去 CPU 使用權。
  • NIO (Non-blocking I/O):非阻塞 I/O,單一 I/O 阻塞不阻塞線程。單線程可管理多 I/O 連接——透過 ServerSocketChannel 接收、向 Selector 註冊輪詢調度、取得就緒連接、利用 Buffer 高效填充資料、快速切換下一就緒連接。
  • Buffer (緩衝區):通道傳輸資料的媒介。NIO 不再直接操作流,改在緩衝區讀寫,有效減少 I/O 中斷次數,優化效能。
  • Channel (通道):負責將緩衝區資料塊移入/移出各類 I/O 來源。
  • Selector (多路複用器):完成就緒選擇。將通道註冊至 Selector,每通道獲一 SelectionKey。程式詢問 Selector 獲知就緒通道,無阻塞完成 I/O,並可查詢鍵集合。

Channel 與 Stream 關鍵差異

  • Stream 基於位元組,Channel 基於區塊——單次傳輸資料量遠大於位元組,效能優勢明顯(Stream 亦可傳位元組陣列)。
  • Channel 緩衝區通常支援雙向讀寫,Stream 單向(唯讀/唯寫),唯 ROM 等特例為唯讀通道。

I/O 效能瓶頸根源

馮·諾依曼架構下,資料在元件間傳輸速率決定執行效率。各級儲存(CPU 暫存器、快取、記憶體、硬碟)速度差距數量級。傳統 BIO 導致高速運算單元大量時間等待資料傳輸。網路環境同理。

傳輸速度階層:CPU > 記憶體 > 磁碟 > 網路(一般情況)。

傳統對策:

  • 緩衝:減少中斷次數(單次傳輸量增大)與中斷時間(緩衝區速率較快)。
  • 多線程:單線程處理單 I/O,不影響 CPU 獲取。但連接數增多導致線程數激增,建立與切換開銷不可忽視,嚴重降低系統效能。

NIO 借鑑多線程思路,進一步優化掉線程建立與切換開銷——單線程非阻塞管理多 I/O 連接,即為 NIO 核心價值。

Channel 與 Buffer 基礎實戰

先建立簡單測試伺服器(每秒發送固定訊息):

public class TestServer {
    public static void main(String[] args) {
        int port = 9999;
        try (ServerSocket server = new ServerSocket(port);
             Socket client = server.accept();
             OutputStream out = client.getOutputStream()) {
            byte[] buffer = "llin of Tianjin University love JAVA and Alibaba!".getBytes();
            while (true) {
                out.write(buffer);
                Thread.sleep(1000);
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

客戶端使用 SocketChannel + ByteBuffer 讀取並輸出至控制台(為示範 API 採阻塞模式,未使用 Selector):

public class TestClient {
    public static final String DEFAULT_IP = "127.0.0.1";
    public static final int DEFAULT_PORT = 9999;
    public static final int BUFFER_SIZE = 200;

    public static void main(String[] args) {
        String ip = args.length > 0 ? args[0] : DEFAULT_IP;
        int port = args.length > 1 ? Integer.parseInt(args[1]) : DEFAULT_PORT;

        try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(ip, port));
             WritableByteChannel out = Channels.newChannel(System.out)) {
            ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
            while (channel.read(buffer) != -1) {
                buffer.flip();  // 切換讀模式:position=0, limit=原 position
                out.write(buffer);
                System.out.write("\n".getBytes());
                buffer.clear(); // 重置 position=0, limit=capacity
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

關鍵點:flip() 將寫模式轉讀模式,clear() 重置供下次寫入。position、limit、capacity 三屬性控制緩衝區操作窗口。

執行效果(需先啟動伺服器):

NIO HTTP 伺服器實戰:Selector 多路複用核心

測試客戶端新增發送請求行:

client.write(ByteBuffer.wrap("testing...".getBytes()));

伺服器端完整實作(註解詳解核心流程):

public class NioHttpServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false); // 關鍵:非阻塞模式
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 註冊接受事件

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            selector.select(); // 阻塞等待就緒事件
            Set keys = selector.selectedKeys();
            Iterator iter = keys.iterator();

            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                iter.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();
                    buffer.clear();
                    int read = client.read(buffer);
                    if (read > 0) {
                        buffer.flip();
                        // 簡易解析 HTTP 請求(略)
                        // 構建回應
                        String response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, NIO!";
                        client.write(ByteBuffer.wrap(response.getBytes()));
                        client.close(); // 簡單示範:短連接
                    }
                }
            }
        }
    }
}

核心流程:

  1. 開啟 Selector,建立非阻塞 ServerSocketChannel 綁定埠並註冊 OP_ACCEPT。
  2. 事件迴圈:selector.select() 阻塞等待就緒事件。
  3. 遍歷 selectedKeys(),處理各就緒事件:
  4. OP_ACCEPT:接受連接,設新通道非阻塞並註冊 OP_READ。
  5. OP_READ:讀取資料,處理業務邏輯,寫回回應(可再註冊 OP_WRITE 處理大量回應)。

測試結果:

此架構單線程支援高併發連接,避免傳統多線程上下文切換開銷,為 Netty 等高效能網路框架基石。

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