????????在 Java 的 I/O 模型中,NIO(Non - Blocking I/O,非阻塞 I/O)是對 BIO 的重要改進。它為高并發場景提供了更高效的處理方式,在眾多 Java 應用中發揮著關鍵作用。
????????NIO模型的核心在于非阻塞和多路復用,其采用 “一個線程處理多個連接” 的模式,主要依靠通道(Channel)、緩沖區(Buffer)和選擇器(Selector)這三個核心組件協同工作,每個核心組件的功能原理和功能如下:
(1)通道:通道是數據傳輸的通道,類似于 BIO 中的流,但它是雙向的,既可以從通道讀取數據,也可以向通道寫入數據。常見的通道有ServerSocketChannel(用于服務器端監聽連接)、SocketChannel(用于客戶端與服務器端的通信)等。?
(2)緩沖區:緩沖區是存儲數據的容器,所有數據的讀寫都必須通過緩沖區進行。它本質上是一個數組,提供了對數據的結構化訪問以及維護讀寫位置等信息。在 NIO 中,數據從通道讀取到緩沖區,或從緩沖區寫入到通道。?
(3)選擇器:選擇器是 NIO 實現多路復用的關鍵。它可以同時監控多個通道的事件(如連接請求、數據可讀、數據可寫等)。一個線程通過選擇器注冊多個通道,然后阻塞在選擇器上,等待通道事件的發生。當有事件發生時,線程會處理這些事件,從而實現一個線程高效處理多個連接。?
????????其工作流程大致為:服務器端通過ServerSocketChannel監聽端口,將其注冊到選擇器上,并設置關注的事件(如接受連接事件)。客戶端通過SocketChannel發起連接。選擇器不斷輪詢注冊的通道,當某個通道有事件發生時,就會被選中。線程從選擇器中獲取這些就緒的通道,進行相應的處理,如接受連接、讀取數據、寫入數據等,且這些操作大多是非阻塞的。?
? ? ? ? 作為BIO的改進型,NIO也是有著許多優點,例如:?
(1)高并發處理能力強:借助多路復用機制,一個線程可以處理多個連接,大大減少了線程的創建和銷毀帶來的開銷,以及線程上下文切換的成本,能在高并發場景下保持較好的性能。?
(2)非阻塞提升效率:在數據讀寫過程中,線程不會一直阻塞等待,當沒有數據可讀或可寫時,線程可以去處理其他通道的事件,提高了線程的利用率。?
(3)雙向傳輸更靈活:通道是雙向的,相比 BIO 中流的單向傳輸,在一些需要雙向數據交互的場景中,使用更方便靈活。?
? ? ? ? 但是同樣的,其缺點也不少,如:?
(1)編程復雜度高:NIO 的編程模型相對 BIO 更為復雜,需要理解通道、緩沖區、選擇器等多個組件的協同工作機制,對開發者的技術要求較高。?
(2)學習門檻較高:其涉及的多路復用、非阻塞等概念較難理解,新手需要花費更多的時間和精力去掌握。?
(3)在低并發場景下優勢不明顯:在并發量較小的情況下,NIO 的優勢難以體現,其復雜的機制可能還會帶來一些額外的開銷。?
????????下面通過一個簡單的客戶端 - 服務器通信示例來展示 Java NIO 的使用。?
服務器端代碼?
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;public class NioServer {public static void main(String[] args) throws IOException {// 創建ServerSocketChannel并綁定端口ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().bind(new InetSocketAddress(8888));// 設置為非阻塞模式serverSocketChannel.configureBlocking(false);// 創建選擇器Selector selector = Selector.open();// 將ServerSocketChannel注冊到選擇器,關注接受連接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服務器啟動,監聽端口8888...");while (true) {// 阻塞等待通道事件,返回就緒的通道數量int readyChannels = selector.select();if (readyChannels == 0) {continue;}// 獲取就緒的事件Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();// 移除處理過的事件,避免重復處理iterator.remove();if (key.isAcceptable()) {// 處理接受連接事件ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel socketChannel = serverChannel.accept();socketChannel.configureBlocking(false);// 將客戶端通道注冊到選擇器,關注數據可讀事件socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("收到新的客戶端連接:" + socketChannel.getRemoteAddress());} else if (key.isReadable()) {// 處理數據可讀事件SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = socketChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客戶端消息:" + message);// 向客戶端發送響應buffer.clear();String response = "服務器已收到消息:" + message;buffer.put(response.getBytes());buffer.flip();socketChannel.write(buffer);} else if (bytesRead == -1) {// 客戶端關閉連接socketChannel.close();System.out.println("客戶端連接關閉");}}}}}
}
/*
創建ServerSocketChannel并綁定端口,設置為非阻塞模式,然后注冊到選擇器上并關注接受連接事件。進入循環后,線程阻塞在選擇器的select()方法上,等待通道事件。當有接受連接事件時,接受客戶端連接,將客戶端的SocketChannel注冊到選擇器并關注數據可讀事件。當有數據可讀事件時,從通道中讀取數據,處理后向客戶端發送響應。
*/
客戶端代碼?
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;public class NioClient {public static void main(String[] args) throws IOException {// 打開SocketChannel并連接服務器SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8888));// 設置為非阻塞模式socketChannel.configureBlocking(false);System.out.println("已連接到服務器");// 向服務器發送數據Scanner scanner = new Scanner(System.in);System.out.println("請輸入要發送的消息:");String message = scanner.nextLine();ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.put(message.getBytes());buffer.flip();socketChannel.write(buffer);// 讀取服務器的響應buffer.clear();int bytesRead = socketChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String response = new String(data);System.out.println("收到服務器響應:" + response);}scanner.close();socketChannel.close();System.out.println("客戶端連接關閉");}
}
/*
創建SocketChannel連接服務器,設置為非阻塞模式,向服務器發送數據,然后讀取服務器的響應,最后關閉連接。
*/
????????從代碼中能清楚看到 NIO 的非阻塞和多路復用特性,一個線程通過選擇器處理多個通道的事件,極大地提高了并發處理能力。?