早些時候,我們了解了IO API(輸入/輸出應用程序編程接口)和java.io包,它們的類主要用於處理 Java 中的流。這裡的關鍵是流的概念。
今天我們將開始考慮NIO API(新輸入/輸出)。
這兩種 I/O 方法的主要區別在於 IO API 是面向流的,而 NIO API 是面向緩衝區的。所以要理解的主要概念是buffers和channels。
什麼是緩衝區,什麼是通道?
通道是數據進出的邏輯入口,而緩衝區是此傳輸數據的源或目的地。在輸出過程中,將要發送的數據放入緩衝區,緩衝區將數據傳遞給通道。在輸入期間,來自通道的數據被放入緩衝區。
換句話說:
- 緩衝區只是一塊內存,我們可以在其中寫入信息,也可以從中讀取信息,
- 通道是提供對 I/O 設備(例如文件或套接字)的訪問的網關。
通道與 java.io 包中的流非常相似。所有去往任何地方(或來自任何地方)的數據都必須通過通道對象。通常,要使用 NIO 系統,您會獲得一個到 I/O 實體的通道和一個用於存儲數據的緩衝區。然後您使用緩衝區,根據需要輸入或輸出數據。
您可以在緩衝區中前後移動,即“遍歷”緩衝區,這是您在流中無法做到的。這在處理數據時提供了更大的靈活性。在標準庫中,緩衝區由抽象Buffer類及其幾個後代表示:
- 字節緩衝區
- 字符緩衝區
- 短緩衝區
- 緩衝區
- 浮動緩衝區
- 雙緩衝
- 長緩衝區
子類之間的主要區別在於它們存儲的數據類型——字節、整數、長整型和其他原始數據類型。
緩衝區屬性
緩衝區有四個主要屬性。這些是容量、限制、位置和標記。
容量是可以存儲在緩衝區中的最大數據量/字節數。緩衝區的容量無法更改。一旦緩衝區已滿,必須先清除緩衝區,然後再向其寫入更多內容。
在寫入模式下,緩衝區的限制與其容量相同,表示可以寫入緩衝區的最大數據量。在讀取模式下,緩衝區的限制是指可以從緩衝區讀取的最大數據量。
position指示光標在緩衝區中的當前位置。最初,它在創建緩衝區時設置為 0。換句話說,它是下一個要讀取或寫入的元素的索引。
該標記用於保存光標位置。當我們操作緩衝區時,游標位置不斷變化,但我們總能將其返回到先前標記的位置。
使用緩衝區的方法
現在讓我們看看主要的方法集,這些方法讓我們可以使用我們的緩衝區(內存塊)從通道讀取數據和從通道寫入數據。
-
allocate(int capacity) — 此方法用於分配具有指定容量的新緩衝區。如果傳遞的容量是負整數,allocate ()方法將拋出IllegalArgumentException 。
-
capacity()返回當前緩衝區的容量。
-
position()返回當前光標位置。讀寫操作將光標移動到緩衝區的末尾。返回值始終小於或等於限制。
-
limit()返回當前緩衝區的限制。
-
mark()用於標記(保存)當前光標位置。
-
reset()將光標返回到先前標記(保存)的位置。
-
clear()將位置設置為零並將限制設置為容量。此方法不會清除緩衝區中的數據。它只會重新初始化位置、限制和標記。
-
flip()將緩衝區從寫模式切換到讀模式。它還將限制設置為當前位置,然後將位置放回零。
-
read() — 通道的 read 方法用於將數據從通道寫入緩衝區,而緩衝區的put()方法用於將數據寫入緩衝區。
-
write() — 通道的 write 方法用於將數據從緩衝區寫入通道,而緩衝區的get()方法用於從緩衝區讀取數據。
-
rewind()倒回緩衝區。當您需要重新讀取緩衝區時使用此方法——它將位置設置為零並且不更改限制。
現在談談渠道。
Java NIO 中最重要的通道實現是以下類:
-
FileChannel — 用於從文件讀取數據和向文件寫入數據的通道。
-
DatagramChannel — 此類通過 UDP(用戶數據報協議)在網絡上讀取和寫入數據。
-
SocketChannel — 通過 TCP(傳輸控制協議)通過網絡讀取和寫入數據的通道。
-
ServerSocketChannel — 一種通過 TCP 連接讀取和寫入數據的通道,就像 Web 服務器一樣。為每個傳入連接創建一個SocketChannel 。
實踐
是時候寫幾行代碼了。首先,讓我們讀取文件並將其內容顯示在控制台上,然後將一些字符串寫入文件。
代碼包含很多註釋——我希望它們能幫助你理解一切是如何工作的:
// Create a RandomAccessFile object, passing in the file path
// and a string that says the file will be opened for reading and writing
try (RandomAccessFile randomAccessFile = new RandomAccessFile("text.txt", "rw");
// Get an instance of the FileChannel class
FileChannel channel = randomAccessFile.getChannel();
) {
// Our file is small, so we'll read it in one go
// Create a buffer of the required size based on the size of our channel
ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
// Read data will be put into a StringBuilder
StringBuilder builder = new StringBuilder();
// Write data from the channel to the buffer
channel.read(byteBuffer);
// Switch the buffer from write mode to read mode
byteBuffer.flip();
// In a loop, write data from the buffer to the StringBuilder
while (byteBuffer.hasRemaining()) {
builder.append((char) byteBuffer.get());
}
// Display the contents of the StringBuilder on the console
System.out.println(builder);
// Now let's continue our program and write data from a string to the file
// Create a string with arbitrary text
String someText = "Hello, Amigo!!!!!";
// Create a new buffer for writing,
// but let the channel remain the same, because we're going to the same file
// In other words, we can use one channel for both reading and writing to a file
// Create a buffer specifically for our string — convert the string into an array and get its length
ByteBuffer byteBuffer2 = ByteBuffer.allocate(someText.getBytes().length);
// Write our string to the buffer
byteBuffer2.put(someText.getBytes());
// Switch the buffer from write mode to read mode
// so that the channel can read from the buffer and write our string to the file
byteBuffer2.flip();
// The channel reads the information from the buffer and writes it to our file
channel.write(byteBuffer2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
試試NIO API——你會愛上它的!
GO TO FULL VERSION