Java NIO入門(二):緩衝區內部細節

Java NIO 入門(二)緩衝區內部細節java

概述數組

本文將介紹 NIO 中兩個重要的緩衝區組件:狀態變量訪問方法 (accessor)。學習

狀態變量是前一文中提到的"內部統計機制"的關鍵。每個讀/寫操做都會改變緩衝區的狀態。經過記錄和跟蹤這些變化,緩衝區就可可以內部地管理本身的資源。大數據

在從通道讀取數據時,數據被放入到緩衝區。在有些狀況下,能夠將這個緩衝區直接寫入另外一個通道,可是在通常狀況下,您還須要查看數據。這是使用訪問方法get()來完成的。一樣,若是要將原始數據放入緩衝區中,就要使用訪問方法put()。this

在本節中,您將學習關於 NIO 中的狀態變量和訪問方法的內容。咱們將描述每個組件,並讓您有機會看到它的實際應用。雖然 NIO 的內部統計機制初看起來可能很複雜,可是您很快就會看到大部分的實際工做都已經替您完成了。您可能習慣於經過手工編碼進行簿記 ― 即便用字節數組和索引變量,如今它已在 NIO 中內部地處理了。編碼

狀態變量spa

能夠用三個值指定緩衝區在任意時刻的狀態:3d

  • positioncode

  • limitblog

  • capacity

這三個變量一塊兒能夠跟蹤緩衝區的狀態和它所包含的數據。咱們將在下面的小節中詳細分析每個變量,還要介紹它們如何適應典型的讀/寫(輸入/輸出)進程。在這個例子中,咱們假定要將數據從一個輸入通道拷貝到一個輸出通道。

1.Position

您能夠回想一下,緩衝區實際上就是美化了的數組。在從通道讀取時,您將所讀取的數據放到底層的數組中。 position 變量跟蹤已經寫了多少數據。更準確地說,它指定了下一個字節將放到數組的哪個元素中。所以,若是您從通道中讀三個字節到緩衝區中,那麼緩衝區的position 將會設置爲3,指向數組中第四個元素。

一樣,在寫入通道時,您是從緩衝區中獲取數據。 position 值跟蹤從緩衝區中獲取了多少數據。更準確地說,它指定下一個字節來自數組的哪個元素。所以若是從緩衝區寫了5個字節到通道中,那麼緩衝區的 position 將被設置爲5,指向數組的第六個元素。

2.Limit

limit 變量代表還有多少數據須要取出(在從緩衝區寫入通道時),或者還有多少空間能夠放入數據(在從通道讀入緩衝區時)。

position 老是小於或者等於 limit。

3.Capacity

緩衝區的 capacity 代表能夠儲存在緩衝區中的最大數據容量。實際上,它指定了底層數組的大小 ― 或者至少是指定了准許咱們使用的底層數組的容量。

limit 決不能大於 capacity。

咱們首先觀察一個新建立的緩衝區。出於本例子的須要,咱們假設這個緩衝區的總容量爲8個字節。 Buffer 的狀態以下所示:

回想一下 ,limit 決不能大於 capacity,此例中這兩個值都被設置爲 8。咱們經過將它們指向數組的尾部以後(若是有第8個槽,則是第8個槽所在的位置)來講明這點。

position 設置爲0。若是咱們讀一些數據到緩衝區中,那麼下一個讀取的數據就進入 slot 0 。若是咱們從緩衝區寫一些數據,從緩衝區讀取的下一個字節就來自 slot 0 。 position 設置以下所示:

 

因爲 capacity 不會改變,因此咱們在下面的討論中能夠忽略它。

第一次讀取

如今咱們能夠開始在新建立的緩衝區上進行讀/寫操做。首先從輸入通道中讀一些數據到緩衝區中。第一次讀取獲得三個字節。它們被放到數組中從 position 開始的位置,這時 position 被設置爲 0。讀完以後,position 就增長到 3,以下所示:

 

limit 沒有改變。

第二次讀取

在第二次讀取時,咱們從輸入通道讀取另外兩個字節到緩衝區中。這兩個字節儲存在由 position 所指定的位置上, position 於是增長 2:

 

limit 沒有改變。

flip

如今咱們要將數據寫到輸出通道中。在這以前,咱們必須調用 flip() 方法。這個方法作兩件很是重要的事:

  1. 它將 limit 設置爲當前 position。

  2. 它將 position 設置爲 0。

前一小節中的圖顯示了在 flip 以前緩衝區的狀況。下面是在 flip 以後的緩衝區:

 

咱們如今能夠將數據從緩衝區寫入通道了。 position 被設置爲 0,這意味着咱們獲得的下一個字節是第一個字節。limit 已被設置爲原來的 position,這意味着它包括之前讀到的全部字節,而且一個字節也很少。

第一次寫入

在第一次寫入時,咱們從緩衝區中取四個字節並將它們寫入輸出通道。這使得 position 增長到 4,而 limit 不變,以下所示:

 

第二次寫入

咱們只剩下一個字節可寫了。 limit在咱們調用 flip() 時被設置爲 5,而且 position 不能超過 limit。因此最後一次寫入操做從緩衝區取出一個字節並將它寫入輸出通道。這使得 position 增長到 5,並保持 limit 不變,以下所示:

 

clear

最後一步是調用緩衝區的 clear() 方法。這個方法重設緩衝區以便接收更多的字節。 Clear 作兩種很是重要的事情:

  1. 它將 limit 設置爲與 capacity 相同。

  2. 它設置 position 爲 0。

下圖顯示了在調用 clear() 後緩衝區的狀態:

 

緩衝區如今能夠接收新的數據了。

訪問方法

到目前爲止,咱們只是使用緩衝區將數據從一個通道轉移到另外一個通道。然而,程序常常須要直接處理數據。例如,您可能須要將用戶數據保存到磁盤。在這種狀況下,您必須將這些數據直接放入緩衝區,而後用通道將緩衝區寫入磁盤。

或者,您可能想要從磁盤讀取用戶數據。在這種狀況下,您要將數據從通道讀到緩衝區中,而後檢查緩衝區中的數據。

在本節的最後,咱們將詳細分析如何使用 ByteBuffer 類的 get() 和 put() 方法直接訪問緩衝區中的數據。

get() 方法

ByteBuffer 類中有四個 get() 方法:

  1. byte get();

  2. ByteBuffer get( byte dst[] );

  3. ByteBuffer get( byte dst[], int offset, int length );

  4. byte get( int index );

第一個方法獲取單個字節。第二和第三個方法將一組字節讀到一個數組中。第四個方法從緩衝區中的特定位置獲取字節。那些返回 ByteBuffer的方法只是返回調用它們的緩衝區的 this 值。

此外,咱們認爲前三個 get() 方法是相對的,而最後一個方法是絕對的。 相對 意味着 get() 操做服從 limit 和 position 值 ― 更明確地說,字節是從當前 position 讀取的,而 position 在 get 以後會增長。另外一方面,一個 絕對 方法會忽略 limit 和 position 值,也不會影響它們。事實上,它徹底繞過了緩衝區的統計方法。

上面列出的方法對應於 ByteBuffer 類。其餘類有等價的 get() 方法,這些方法除了不是處理字節外,其它方面是是徹底同樣的,它們處理的是與該緩衝區類相適應的類型。

put()方法

ByteBuffer 類中有五個 put() 方法:

  1. ByteBuffer put( byte b );

  2. ByteBuffer put( byte src[] );

  3. ByteBuffer put( byte src[], int offset, int length );

  4. ByteBuffer put( ByteBuffer src );

  5. ByteBuffer put( int index, byte b );

第一個方法 寫入(put) 單個字節。第二和第三個方法寫入來自一個數組的一組字節。第四個方法將數據從一個給定的源 ByteBuffer 寫入這個 ByteBuffer。第五個方法將字節寫入緩衝區中特定的 位置 。那些返回 ByteBuffer 的方法只是返回調用它們的緩衝區的 this 值。

與 get() 方法同樣,咱們將把 put() 方法劃分爲 相對 或者 絕對 的。前四個方法是相對的,而第五個方法是絕對的。

上面顯示的方法對應於 ByteBuffer 類。其餘類有等價的 put() 方法,這些方法除了不是處理字節以外,其它方面是徹底同樣的。它們處理的是與該緩衝區類相適應的類型。

舉一個簡單的例子,以下代碼所示:

 1 // TypesInByteBuffer
 2 
 3 import java.nio.*;
 4 
 5 public class TypesInByteBuffer {
 6   static public void main( String[] args) throws Exception {
 7     ByteBuffer buffer=ByteBuffer.allocate(64);
 8     ByteBuffer buffer2=ByteBuffer.allocate(1024);
 9     
10     buffer.putInt(30);
11     buffer.putDouble(Math.PI);
12     buffer.flip();
13 //    System.out.println(buffer.getInt());
14 //    System.out.println(buffer.getDouble());
15 //    buffer.clear();
16     
17     buffer2.put(buffer);
18     buffer2.flip();
19     System.out.println(buffer2.getInt());
20     System.out.println(buffer2.getDouble());
21   }
22 }

類型化的 get() 和 put() 方法

除了前些小節中描述的 get() 和 put() 方法, ByteBuffer 還有用於讀寫不一樣類型的值的其餘方法,以下所示:

  • getByte()

  • getChar()

  • getShort()

  • getInt()

  • getLong()

  • getFloat()

  • getDouble()

  • putByte()

  • putChar()

  • putShort()

  • putInt()

  • putLong()

  • putFloat()

  • putDouble()

事實上,這其中的每一個方法都有兩種類型 ― 一種是相對的,另外一種是絕對的。它們對於讀取格式化的二進制數據(如圖像文件的頭部)頗有用。

 1 // TypesInByteBuffer
 2 
 3 import java.nio.*;
 4 
 5 public class TypesInByteBuffer {
 6   static public void main( String[] args) throws Exception {
 7     ByteBuffer buffer=ByteBuffer.allocate(64);
 8 
 9     buffer.putInt(30);
10     buffer.putDouble(Math.PI);
11     buffer.flip();
12     System.out.println(buffer.getInt());
13     System.out.println(buffer.getDouble());
14     buffer.clear();
15   }
16 }

緩衝區的使用:一個內部循環

下面的內部循環歸納了使用緩衝區將數據從輸入通道拷貝到輸出通道的過程。

1 while (true) {
2      buffer.clear();
3      int r = fcin.read( buffer );
4      if (r==-1) {
5        break;
6      }
7      buffer.flip();
8      fcout.write( buffer );
9 }

read() 和 write() 調用獲得了極大的簡化,由於許多工做細節都由緩衝區完成了。 clear() 和 flip() 方法用於讓緩衝區在讀和寫之間切換。

相關文章
相關標籤/搜索