早些時候,我們了解了IO API(輸入/輸出應用程序編程接口)和java.io包,它們的類主要用於處理 Java 中的流。這裡的關鍵是流的概念。

今天我們將開始考慮NIO API(新輸入/輸出)。

這兩種 I/O 方法的主要區別在於 IO API 是面向流的,而 NIO API 是面向緩衝區的。所以要理解的主要概念是bufferschannels

什麼是緩衝區,什麼是通道?

通道是數據進出的邏輯入口,而緩衝區是此傳輸數據的源或目的地在輸出過程中,將要發送的數據放入緩衝區,緩衝區將數據傳遞給通道。在輸入期間,來自通道的數據被放入緩衝區。

換句話說:

  • 緩衝區只是一塊內存,我們可以在其中寫入信息,也可以從中讀取信息
  • 通道是提供對 I/O 設備(例如文件或套接字的訪問的網關。

通道與 java.io 包中的流非常相似。所有去往任何地方(或來自任何地方)的數據都必須通過通道對象。通常,要使用 NIO 系統,您會獲得一個到 I/O 實體的通道和一個用於存儲數據的緩衝區。然後您使用緩衝區,根據需要輸入或輸出數據。

您可以在緩衝區中前後移動,即“遍歷”緩衝區,這是您在流中無法做到的。這在處理數據時提供了更大的靈活性。在標準庫中,緩衝區由抽象Buffer類及其幾個後代表示:

  • 字節緩衝區
  • 字符緩衝區
  • 短緩衝區
  • 緩衝區
  • 浮動緩衝區
  • 雙緩衝
  • 長緩衝區

子類之間的主要區別在於它們存儲的數據類型——字節整數長整型和其他原始數據類型。

緩衝區屬性

緩衝區有四個主要屬性。這些是容量、限制、位置和標記。

容量是可以存儲在緩衝區中的最大數據量/字節數。緩衝區的容量無法更改。一旦緩衝區已滿,必須先清除緩衝區,然後再向其寫入更多內容。

在寫入模式下,緩衝區的限制與其容量相同,表示可以寫入緩衝區的最大數據量。在讀取模式下,緩衝區的限制是指可以從緩衝區讀取的最大數據量。

position指示光標在緩衝區中的當前位置最初,它在創建緩衝區時設置為 0。換句話說,它是下一個要讀取或寫入的元素的索引。

標記用於保存光標位置。當我們操作緩衝區時,游標位置不斷變化,但我們總能將其返回到先前標記的位置。

使用緩衝區的方法

現在讓我們看看主要的方法集,這些方法讓我們可以使用我們的緩衝區(內存塊)從通道讀取數據和從通道寫入數據。

  1. allocate(int capacity) — 此方法用於分配具有指定容量的新緩衝區。如果傳遞的容量是負整數,allocate ()方法將拋出IllegalArgumentException 。

  2. capacity()返回當前緩衝區的容量

  3. position()返回當前光標位置。讀寫操作將光標移動到緩衝區的末尾。返回值始終小於或等於限制。

  4. limit()返回當前緩衝區的限制。

  5. mark()用於標記(保存)當前光標位置。

  6. reset()將光標返回到先前標記(保存)的位置。

  7. clear()將位置設置為零並將限制設置為容量。此方法不會清除緩衝區中的數據。它只會重新初始化位置、限制和標記。

  8. flip()將緩衝區從寫模式切換到讀模式。它還將限制設置為當前位置,然後將位置放回零。

  9. read() — 通道的 read 方法用於將數據從通道寫入緩衝區,而緩衝區的put()方法用於將數據寫入緩衝區。

  10. write() — 通道的 write 方法用於將數據從緩衝區寫入通道,而緩衝區的get()方法用於從緩衝區讀取數據。

  11. rewind()倒回緩衝區。當您需要重新讀取緩衝區時使用此方法——它將位置設置為零並且不更改限制。

現在談談渠道。

Java NIO 中最重要的通道實現是以下類:

  1. FileChannel — 用於從文件讀取數據和向文件寫入數據的通道。

  2. DatagramChannel — 此類通過 UDP(用戶數據報協議)在網絡上讀取和寫入數據。

  3. SocketChannel — 通過 TCP(傳輸控制協議)通過網絡讀取和寫入數據的通道。

  4. 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——你會愛上它的!