Java NIO Channel & Buffer(Java NIO 通道和緩存)

本文翻譯自: http://tutorials.jenkov.com/java-nio/index.html, 本人第一次開始寫博客,第一次翻譯,如有問題,歡迎指正~

上一篇JAVA NIO 概述

Java NIO Channel

Java NIO Channel 和 Java IO Stream 非常相似,不過也有一點區別:

  • 我們可以同時向一個 Channel 讀出和寫入,但是 Stream卻只能單方向的讀或者寫
  • 我們可以異步的讀寫一個Channel
  • Channel 總是讀出數據到一個Buffer,或者從一個Buffer中寫入

下面是一個圖示說明,和上篇博客的圖示一樣,這裏再次引用:
Channel讀出數據到一個Buffer,Buffer中的數據寫入Channel

Channel的實現類

這是 Java NIO 中幾個比較重要的Channel實現類:

  • FileChannel -> 能夠從文件中讀出數據或向文件中寫入數據
  • DatagramChannel -> 能夠使用UDP協議在網絡中收發數據
  • SocketChannel -> 能夠使用TCP協議在網絡中收發數據
  • ServerSocketChannel -> 能夠像Web服務器那樣,監聽到來的TCP連接,併爲此TCP連接創建SocketChannel

Channel的簡單示例代碼

下面是使用FileChannel從Buffer中讀入數據的簡單示例:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {

  System.out.println("Read " + bytesRead);
  buf.flip();

  while(buf.hasRemaining()){
      System.out.print((char) buf.get());
  }

  buf.clear();
  bytesRead = inChannel.read(buf);
}
aFile.close();

請注意調用buf.flip(),這是ByteBuffer的讀和寫的翻轉函數。開始時,我們將數據從Channel讀入到Buffer,接着我們將Buffer從寫入模式翻轉(flip)到讀出模式,然後,我們可以將數據從Buffer中讀出。本文將在下面詳細介紹Java NIO Buffer。

Java NIO Buffer

當要和Java NIO Channel交互時,需要使用 Java NIO Buffer。如上文所講,數據從Channel中讀出存入Buffer,數據從Buffer讀出寫入Channel。
基本上來說,Buffer就是一塊內存。我們可以向其中寫入數據,也可以從中讀出數據。這個內存塊被一個提供了一系列地非常方便地操作內存塊方法的NIO Java類封裝了起來。

Buffer基本用法

使用一個Buffer去讀寫數據一般需要這4個過程:

  • 向Buffer中寫入數據
  • 調用buffer.flip()讀寫模式翻轉函數
  • 將數據從Buffer中讀出
  • 調用buffer.clear()清空緩存或者調用buffer.compact()清除已讀數據

當我們向Buffer寫入數據時,Buffer會一直記錄着寫入數據的數量。一旦我們需要從Buffer中讀出數據時,首先需要調用flip()函數將Buffer從寫入模式翻轉到讀出模式,然後可以從中讀出所以寫入到Buffer中的數據(注意:一般flip()只用來將Buffer從寫入模式翻轉成讀模式。當需要將Buffer從讀模式翻轉成寫模式時,調用clear() 或 compact()。其實flip()方法的代碼和clear()是一樣的)。

public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
}
public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
}

一旦Buffer中的所有數據,我們需要清理Buffer,爲再次寫入做準備。有兩個函數可以實現清理操作:clear() 和 compact() 。clear()方法會清空整個Buffer內存塊。而compact()方法僅清楚我們已經讀出過的數據,並將未讀的數據移動到內存塊的起始位置,以後新寫入的數據將放入到緊接在未讀數據之後的位置。
下面是一個帶有寫、翻轉、讀和清除操作的簡單Buffer使用示例:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {

  buf.flip();  //make buffer ready for read

  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }

  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();
while (bytesRead != -1) {

  buf.flip();  //make buffer ready for read

  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }

  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();

Buffer Capacity, Position and Limit(容量、位置和限制)

正如前面所講,Buffer是一個內存塊。爲了完成一些操作,Buffer需要一些變量來記錄這個內存塊的屬性。同時爲了更好的理解Buffer是怎樣工作的,接下來將講解着三個屬性:

  • capacity 容量
  • position 操作位置
  • limit 操作限制位置

position 和 limit 代表的意義和Buffer是處於讀模式或者寫模式相關,而不管Buffer處在什麼模式下,capacity 的意義不變。
下面是一張寫模式和讀模式下的capacity 、position 和limit 的圖示說明。下文對這三個屬性的講解也是參考此圖。

讀或寫模式下,Buffer的capacity , position , limit 的意義

Capacity

Buffer管理一個內存塊有固定的大小,這個大小成爲“容量(Capacity)”。我們只能夠向Buffer中寫入和Capacity相同大小的字節,long型數或字符等。一旦Buffer寫滿,在想要寫入新的數據前,必須清空Buffer,即把數據讀出,並調用clear()或compact()方法。

Position

當向Buffer寫入數據時,數據將被存入到Buffer中的內存塊上的一個特定位置。在初始情況下,position值爲0,即代表內存塊的起始位置。當一個字節或long型數組寫入到Buffer後,position將指向下一個存儲單元以便以後插入數據。position最大能達到capacity - 1。
當從一個Buffer中讀數據時,同樣需要指定一個讀的起始位置。當我們將一個Buffer從寫模式翻轉成讀模式時,即調用flip()方法,position值復位成0。這時如果我們讀一個數據,將讀出position指向位置的數據,並且position會加1指向下一個存儲單元。

Limit

在寫模式時,limit表示我們能想Buffer中寫入多少數據,此時在默認情況下limit 的值和capacity 相等。當然也可以在Buffer處於寫模式中時,調用limit(int newLimit)方法,設置能寫入的數據小於capacity 的限制。
當Buffer翻轉到讀模式時,limit 意味着我們能從緩存中讀出多少數據。一次Buffer翻轉到讀模式時,limit 的值設爲Buffer處在寫模式時的position 的值。也就是說,我們最多隻能讀出和寫入數據數量相同的數據。

常見Buffer的子類(Buffer Types)

Java NIO 有以下一種常見Buffer類型:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

很容易理解,這些不同的Buffer用來緩存不同的數據類型。例如,我們可以用CharBuffer緩存Char型的數據。其中MappedByteBuffer有點特殊,將在以後的章節中專門對它講解。

分配一個緩存區(Allocating a Buffer)

想要獲取一個Buffer對象,首先要爲它分配內存。每個Buffer類都有一個allocate() 靜態方法作爲產生相應Buffer對象的工廠方法。下面是一個產生容量爲48字節的ByteBuffer對象的示例代碼:

ByteBuffer buf = ByteBuffer.allocate(48);

下面是一個產生容量爲1024個字符的CharBuffer對象的示例代碼:

CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中寫入數據

我們有兩種方式向Buffer中寫入數據:

  • 從Channel中讀出數據並寫向Buffer

    int bytesRead = inChannel.read(buf); //read into buffer

  • 調用put() 方法,手動向Buffer中寫入數據

    buf.put(127);

put()方法有多個重載,方便我們以不同的方式向Buffer中寫入數據。例如,在Buffer的特定位置寫入數據,或者向Buffer中寫入一整個數組的數據等。想要獲取更詳細的信息可以閱讀具體某個類的實現文檔(JavaDoc)。

從Buffer中讀出數據

像寫入數據一樣,同樣有兩種方式從Buffer中讀出數據:

  • 從Buffer中讀出數據並寫入Channel

    //read from buffer into channel.
    int bytesWritten = inChannel.write(buf);

  • 調用get()方法,手動從Buffer中讀數據

    byte aByte = buf.get();

get()方法同put()方法一樣有多個重載,提供不同的方式從Buffer中讀出數據。例如,從Buffer的特定位置讀出數據,或者一次從Buffer中讀出一組數據。

flip()

flip()方法將Buffer從寫入模式切換到讀出模式,調用flip()時,position值設定爲0,limit 值設爲Buffer處在寫入模式時的position 的值。
換句話說,position 現在標記着讀的起始位置,limit 現在標記了有多少個數據可以讀。

rewind()

調用rewind()時,position 復位成0,所以此時我們可以再次讀出Buffer中的所有數據。在這個方法中,並沒有涉及到 limit值,因此它依然標記着Buffer中還有多少個數據可以讀。

   public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

clear() and compact()

一旦我們完成從Buffer中讀出數據,需要讓Buffer 翻轉到寫入模式,爲再次寫入數據做好準備。此時我們可以調用clear() 或 compact() 完成此功能。
如果調用clear() 方法,position 復位成0, limit 值設定爲capacity 的值。可以說此時Buffer被清空了,但是Buffer中的數據並沒有被刪除。只是我們可以從Buffer管理的內存塊的起始位置開始寫入數據,覆蓋掉之前的數據。
如果Buffer中還有未讀取的數據,調用clear()會遺棄這些未讀取的數據。這意味着再也沒有標記能告訴哪些數據已經讀取過,哪些數據還未讀取。
如果Buffer中還有未讀取的數據,並且我們以後還需要讀取它,只是現在有一些新的數據需要寫入Buffer,此時應該調用compact(),而非clear()。
compact() 方法將Buffer中未讀的數據複製到內存塊的起始位置,然後將 position 指向緊靠着未讀數據的最後一個元素的後面, limit 依然設爲capacity 的值。現在,Buffer便可接着將新的數據寫在未讀數據的後面。

mark() and reset()

equals() and compareTo()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章