JDK 1.4 引入的 java.nio (New I/O / Non-blocking I/O) 徹底改變了 Java 網絡編程範式。本文將對比傳統 BIO、NIO 核心三大組件、阻塞與非阻塞模型差異、Selector 事件驅動機制、Channel 類型與實戰代碼,帶你構建高性能網絡服務的核心認知。
| 維度 | 傳統 BIO (Blocking I/O) | NIO (Non-blocking I/O) |
|---|---|---|
| 核心抽象 | Stream (流,單向) | Channel (通道,雙向) + Buffer (緩衝區) |
| 線程模型 | 一連接一線程 (Thread-per-Connection) | 一線程多連接 (Reactor 模式) |
| 阻塞特性 | read/write 同步阻塞 | 非阻塞,就緒才處理 |
| 擴展性 | 連接數↑ 線程數↑ 上下文切換開銷大 | 單線程管理萬級連接,高伸縮性 |
| 數據處理 | 流式順序讀寫,無隨機訪問 | 緩衝區隨機讀寫,可前後移動指針 |
ByteBuffer、CharBuffer、IntBuffer 等capacity (容量)、limit (限制)、position (位置)、mark (標記)put() 寫入、flip() 切換讀模式、get() 讀取、clear()/compact() 重置ByteBuffer.allocateDirect() 分配堆外內存,減少拷貝,適合高性能場景| Channel 類型 | 用途 | 對應協議 |
|---|---|---|
FileChannel | 文件讀寫、內存映射、鎖 | 本地文件 |
SocketChannel | 客戶端 TCP 連接 | TCP |
ServerSocketChannel | 服務端監聽 TCP 連接 | TCP |
DatagramChannel | UDP 數據報收發 | UDP |
關鍵差異:Channel 雙向可讀寫,Stream 單向;Channel 必須配合 Buffer 操作。
OP_CONNECT、OP_ACCEPT、OP_READ、OP_WRITESelectionKey 封裝「Channel + Selector + 就緒操作集合」Accept 阻塞 → Read 阻塞 → Process → Write 阻塞 每連接一線程 → 大量上下文切換 → 多數切換無意義 (空閒等待)
單線程 Selector 轮詢 → 事件就緒分發 → Handler 處理 (Read/Write/Accept) 任務切分協作 → 只有有意義的切換 → 高併發低延遲
public class NioFileWrite {
public static void main(String[] args) throws IOException {
File file = new File("data.txt");
try (FileOutputStream fos = new FileOutputStream(file);
FileChannel channel = fos.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Java NIO 非阻塞寫入測試".getBytes());
buffer.flip(); // 關鍵:切換讀模式,position=0, limit=數據長度
channel.write(buffer);
}
}
}
public class NioServer {
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);
while (true) {
selector.select(); // 阻塞等待事件
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (key.isAcceptable()) {
accept(key, selector);
} else if (key.isReadable()) {
read(key);
} else if (key.isWritable()) {
write(key);
}
}
}
}
static void accept(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
static void read(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int len = client.read(buffer);
if (len > 0) {
buffer.flip();
System.out.println("收到: " + StandardCharsets.UTF_8.decode(buffer));
key.interestOps(SelectionKey.OP_WRITE); // 註冊寫事件
}
}
static void write(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.wrap("Server ACK".getBytes());
client.write(buffer);
key.interestOps(SelectionKey.OP_READ); // 切回讀事件
}
}
channel.read/write 可能不完整,需循環直到 buffer.hasRemaining() 為 falsekey.attach(buffer) 綁定每連接專屬 Bufferselector.select(timeout) 或 wakeup() 避免 CPU 空轉key.cancel() + channel.close() 防洩漏Cleaner 或池化,大文件/高頻網絡 IO 優先考慮Path/Files/FileSystem 現代文件 API,支援屬性、遍歷、監聽AsynchronousSocketChannel、CompletionHandler 回調/Future 模式,真正異步,由 OS 完成通知,適合超高併發 (Windows IOCP / Linux epoll)NIO 以「Channel + Buffer + Selector」三劍合璧,將網絡編程從「線程阻塞等待」轉向「事件驅動多路複用」,是 Netty、Mina、Undertow 等高性能網絡框架的基石。掌握其核心模型與 Buffer 狀態機,是構建百萬級併發系統的入場券。下一篇我們將實戰 Netty 源碼級原理剖析。