(二)NIO入門之緩衝區內部細節詳解
緩衝區內部細節
概述
本節將介紹 NIO 中兩個重要的緩衝區元件:狀態變數和訪問方法 (accessor)。
狀態變數是前一節中提到的"內部統計機制"的關鍵。每一個讀/寫操作都會改變緩衝區的狀態。通過記錄和跟蹤這些變化,緩衝區就可能夠內部地管理自己的資源。
在從通道讀取資料時,資料被放入到緩衝區。在有些情況下,可以將這個緩衝區直接寫入另一個通道,但是在一般情況下,您還需要檢視資料。這是使用 訪問方法 get()
來完成的。同樣,如果要將原始資料放入緩衝區中,就要使用訪問方法 put()
。
在本節中,您將學習關於 NIO 中的狀態變數和訪問方法的內容。我們將描述每一個元件,並讓您有機會看到它的實際應用。雖然 NIO 的內部統計機制初看起來可能很複雜,但是您很快就會看到大部分的實際工作都已經替您完成了。您可能習慣於通過手工編碼進行簿記 ― 即使用位元組陣列和索引變數,現在它已在 NIO 中內部地處理了。
狀態變數
可以用三個值指定緩衝區在任意時刻的狀態:
position
limit
capacity
這三個變數一起可以跟蹤緩衝區的狀態和它所包含的資料。我們將在下面的小節中詳細分析每一個變數,還要介紹它們如何適應典型的讀/寫(輸入/輸出)程序。在這個例子中,我們假定要將資料從一個輸入通道拷貝到一個輸出通道。
Position
您可以回想一下,緩衝區實際上就是美化了的陣列。在從通道讀取時,您將所讀取的資料放到底層的陣列中。 position
變數跟蹤已經寫了多少資料。更準確地說,它指定了下一個位元組將放到陣列的哪一個元素中。因此,如果您從通道中讀三個位元組到緩衝區中,那麼緩衝區的 position
同樣,在寫入通道時,您是從緩衝區中獲取資料。 position
值跟蹤從緩衝區中獲取了多少資料。更準確地說,它指定下一個位元組來自陣列的哪一個元素。因此如果從緩衝區寫了5個位元組到通道中,那麼緩衝區的 position
將被設定為5,指向陣列的第六個元素。
Limit
limit
變量表明還有多少資料需要取出(在從緩衝區寫入通道時),或者還有多少空間可以放入資料(在從通道讀入緩衝區時)。
position
總是小於或者等於 limit
。
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()
方法。這個方法做兩件非常重要的事:
- 它將
limit
設定為當前position
。 - 它將
position
設定為 0。
前一小節中的圖顯示了在 flip 之前緩衝區的情況。下面是在 flip 之後的緩衝區:
我們現在可以將資料從緩衝區寫入通道了。 position
被設定為 0,這意味著我們得到的下一個位元組是第一個位元組。 limit
已被設定為原來的 position
,這意味著它包括以前讀到的所有位元組,並且一個位元組也不多。
第一次寫入
在第一次寫入時,我們從緩衝區中取四個位元組並將它們寫入輸出通道。這使得 position
增加到 4,而 limit
不變,如下所示:
第二次寫入
我們只剩下一個位元組可寫了。 limit
在我們呼叫 flip()
時被設定為 5,並且 position
不能超過 limit
。所以最後一次寫入操作從緩衝區取出一個位元組並將它寫入輸出通道。這使得 position
增加到 5,並保持 limit
不變,如下所示:
clear
最後一步是呼叫緩衝區的 clear()
方法。這個方法重設緩衝區以便接收更多的位元組。 Clear
做兩種非常重要的事情:
- 它將
limit
設定為與capacity
相同。 - 它設定
position
為 0。
下圖顯示了在呼叫 clear()
後緩衝區的狀態:
緩衝區現在可以接收新的資料了。
訪問方法
到目前為止,我們只是使用緩衝區將資料從一個通道轉移到另一個通道。然而,程式經常需要直接處理資料。例如,您可能需要將使用者資料儲存到磁碟。在這種情況下,您必須將這些資料直接放入緩衝區,然後用通道將緩衝區寫入磁碟。
或者,您可能想要從磁碟讀取使用者資料。在這種情況下,您要將資料從通道讀到緩衝區中,然後檢查緩衝區中的資料。
在本節的最後,我們將詳細分析如何使用 ByteBuffer
類的 get()
和 put()
方法直接訪問緩衝區中的資料。
get() 方法
ByteBuffer
類中有四個 get()
方法:
byte get();
ByteBuffer get( byte dst[] );
ByteBuffer get( byte dst[], int offset, int length );
byte get( int index );
第一個方法獲取單個位元組。第二和第三個方法將一組位元組讀到一個數組中。第四個方法從緩衝區中的特定位置獲取位元組。那些返回 ByteBuffer
的方法只是返回呼叫它們的緩衝區的 this
值。
此外,我們認為前三個 get()
方法是相對的,而最後一個方法是絕對的。 相對 意味著 get()
操作服從 limit
和 position
值 ― 更明確地說,位元組是從當前 position
讀取的,而 position
在 get
之後會增加。另一方面,一個 絕對 方法會忽略 limit
和 position
值,也不會影響它們。事實上,它完全繞過了緩衝區的統計方法。
上面列出的方法對應於 ByteBuffer
類。其他類有等價的 get()
方法,這些方法除了不是處理位元組外,其它方面是是完全一樣的,它們處理的是與該緩衝區類相適應的型別。
put()方法
ByteBuffer
類中有五個 put()
方法:
ByteBuffer put( byte b );
ByteBuffer put( byte src[] );
ByteBuffer put( byte src[], int offset, int length );
ByteBuffer put( ByteBuffer src );
ByteBuffer put( int index, byte b );
第一個方法 寫入(put)
單個位元組。第二和第三個方法寫入來自一個數組的一組位元組。第四個方法將資料從一個給定的源ByteBuffer
寫入這個 ByteBuffer
。第五個方法將位元組寫入緩衝區中特定的 位置
。那些返回 ByteBuffer
的方法只是返回呼叫它們的緩衝區的 this
值。
與 get()
方法一樣,我們將把 put()
方法劃分為 相對 或者 絕對 的。前四個方法是相對的,而第五個方法是絕對的。
上面顯示的方法對應於 ByteBuffer
類。其他類有等價的 put()
方法,這些方法除了不是處理位元組之外,其它方面是完全一樣的。它們處理的是與該緩衝區類相適應的型別。
型別化的 get() 和 put() 方法
除了前些小節中描述的 get()
和 put()
方法, ByteBuffer
還有用於讀寫不同型別的值的其他方法,如下所示:
getByte()
getChar()
getShort()
getInt()
getLong()
getFloat()
getDouble()
putByte()
putChar()
putShort()
putInt()
putLong()
putFloat()
putDouble()
事實上,這其中的每個方法都有兩種型別 ― 一種是相對的,另一種是絕對的。它們對於讀取格式化的二進位制資料(如影象檔案的頭部)很有用。
您可以在例子程式 TypesInByteBuffer.java 中看到這些方法的實際應用。
緩衝區的使用:一個內部迴圈
下面的內部迴圈概括了使用緩衝區將資料從輸入通道拷貝到輸出通道的過程。
1 2 3 4 5 6 7 8 9 10 11 |
|
read()
和 write()
呼叫得到了極大的簡化,因為許多工作細節都由緩衝區完成了。 clear()
和 flip()
方法用於讓緩衝區在讀和寫之間切換。