NIO 和 NIO.2 有什麼區別?
- java
- Nio
一、簡介
在本教程中,我們將介紹 Java IO 功能以及它們在不同 Java 版本中的變化。首先,我們將介紹初始 Java 版本中java.io
接下來,我們將java.nio
Java 1.4 中引入的 java.nio 包。最後,我們將介紹java.nio.file
包,通常稱為 NIO.2 包。
2.Java NIO 包
第一個 Java 版本與java.io
包一起發布,引入a File
類來訪問文件系統。 File
類表示文件和目錄,並提供對文件系統的有限操作。可以創建和刪除文件,檢查它們是否存在,檢查讀/寫訪問等。
它也有一些缺點:
- 缺少複製方法——要復制一個文件,我們需要創建兩個
File
實例並使用一個緩衝區來讀取一個並寫入另一個File
實例。 - 錯誤處理錯誤——一些方法返回
boolean
作為操作成功與否的指示符。 - 一組有限的文件屬性——名稱、路徑、讀/寫權限、可用內存大小等等。
- 阻塞 API——我們的線程被阻塞,直到 IO 操作完成。
要讀取文件,我們需要一個FileInputStream
實例來從文件中讀取字節:
@Test
public void readFromFileUsingFileIO() throws Exception {
File file = new File("src/test/resources/nio-vs-nio2.txt");
FileInputStream in = new FileInputStream(file);
StringBuilder content = new StringBuilder();
int data = in.read();
while (data != -1) {
content.append((char) data);
data = in.read();
}
in.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
接下來,Java 1.4 引入了捆綁在java.nio
包中的非阻塞 IO API(nio 代表新 IO)。引入 NIO 是為了克服java.io
包的限制。這個包引入了三個核心類: Channel
、 Buffer
和Selector
。
2.1 Channel
Java NIO Channel
是一個允許我們讀取和寫入緩衝區的類。 Channel
類類似於Streams
(這裡我們說的是 IO Streams
,而不是 Java 1.8 Streams
),但有一些不同之處。 Channel
是雙向的,而Streams
通常是單向的,它們可以異步讀寫。
Channel
類有幾個實現,包括FileChannel
用於文件系統讀/寫, DatagramChannel
用於使用 UDP 在網絡上讀/寫,以及SocketChannel
用於使用 TCP 在網絡上讀/寫。
2.2 Buffer
緩衝區是一塊內存,我們可以從中讀取或寫入數據。 NIO Buffer
對象包裝了一個內存塊。 Buffer
類提供了一組與內存塊一起工作的功能。要使用Buffer
對象,我們需要了解Buffer
類的三個主要屬性:容量、位置和限制。
- 容量定義了內存塊的大小。當我們將數據寫入緩衝區時,我們只能寫入有限的長度。當緩衝區已滿時,我們需要讀取數據或清除數據。
- 位置是我們寫入數據的起點。一個空緩衝區從 0 開始到
capacity – 1
。另外,當我們讀取數據時,我們從位置值開始。 - 限制意味著我們如何從緩衝區寫入和讀取。
Buffer
類有多種變體。每個原始 Java 類型一個,不包括Boolean
類型和MappedByteBuffer
。
要使用緩衝區,我們需要知道一些重要的方法:
-
allocate(int value) –
我們使用這個方法來創建一個特定大小的緩衝區。 -
flip()
- 此方法用於從寫入模式切換到讀取模式 -
clear() –
清除緩衝區內容的方法 -
compact() –
只清除我們已經閱讀的內容的方法 -
rewind() –
位置重置為 0,以便我們可以重新讀取緩衝區中的數據
使用前面描述的概念,讓我們使用Channel
和Buffer
類從文件中讀取內容:
@Test
public void readFromFileUsingFileChannel() throws Exception {
RandomAccessFile file = new RandomAccessFile("src/test/resources/nio-vs-nio2.txt", "r");
FileChannel channel = file.getChannel();
StringBuilder content = new StringBuilder();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
content.append((char) buffer.get());
}
buffer.clear();
bytesRead = channel.read(buffer);
}
file.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
在初始化所有需要的對像後,我們從通道讀取到緩衝區。接下來,在 while 循環中,我們使用flip()
方法標記要讀取的緩衝區,並一次讀取一個字節,並將其附加到我們的結果中。最後,我們清除數據並讀取另一批。
2.3. Selector
Java NIO Selector 允許我們用一個線程管理多個通道。要使用選擇器對象監控多個通道,每個通道實例必須處於非阻塞模式,並且我們必須註冊它。通道註冊後,我們得到一個SelectionKey
對象,表示通道和選擇器之間的連接。當我們有多個通道連接到一個選擇器時,我們可以使用select()
方法來檢查有多少通道可供使用。調用select()
方法後,我們可以使用selectedKeys()
方法獲取所有準備好的通道。
2.4. NIO封裝的缺點
java.nio
包引入的變化更多與底層數據 IO 相關。雖然他們允許非阻塞 API,但其他方面仍然存在問題:
- 對符號鏈接的有限支持
- 對文件屬性訪問的有限支持
- 缺少更好的文件系統管理工具
3.Java NIO.2 包
Java 1.7 引入了新的java.nio.file
包,也稱為 NIO.2 包。 java.nio
包中不支持的非阻塞 IO 的異步方法。最重要的變化與高級文件操作有關。它們與Files, Path,
和Paths
類一起添加。最顯著的低級更改是添加了AsynchroniousFileChannel
和AsyncroniousSocketChannel
。
Path
對象表示由分隔符分隔的目錄和文件名的分層序列。根組件在最左邊,而文件在右邊。此類提供實用方法,例如getFileName()
、 getParent()
等。 Path
類還提供resolve
和relativize
方法,幫助在不同文件之間構建路徑。 Paths 類是一組靜態實用程序方法,它們接收String
或URI
以創建Path
實例。
Files
類提供了使用前面描述的Path
類並對文件、目錄和符號鏈接進行操作的實用方法。它還提供了一種使用readAttributes()
方法讀取許多文件屬性的方法。
最後,讓我們看看 NIO.2 在讀取文件時與之前的 IO 版本相比如何:
@Test
public void readFromFileUsingNIO2() throws Exception {
List<String> strings = Files.readAllLines(Paths.get("src/test/resources/nio-vs-nio2.txt"));
assertThat(strings.get(0)).isEqualTo("Hello from file!");
}
4.結論
在本文中,我們介紹了java.nio
和java.nio.file
包的基礎知識。我們可以看到,NIO.2 並不是 NIO 包的新版本。 NIO 包引入了用於非阻塞 IO 的低級 API,而 NIO.2 引入了更好的文件管理。這兩個包不是同義詞,而是相互補充。