天天看點

NIO入門系列之第4章:緩沖區内部細節

4.1  概述

本節将介紹 NIO 中兩個重要的緩沖區元件:狀态變量和通路方法 (accessor)。

狀态變量是前一節中提到的"内部統計機制"的關鍵。每一個讀/寫操作都會改變緩沖區的狀态。通過記錄和跟蹤這些變化,緩沖區就可能夠内部地管理自己的資源。

在從通道讀取資料時,資料被放入到緩沖區。在有些情況下,可以将這個緩沖區直接寫入另一個通道,但是在一般情況下,您還需要檢視資料。這時使用通路方法 get() 來完成的。同樣,如果要将原始資料放入緩沖區中,就要使用通路方法 put()。

在本節中,您将學習關于NIO 中的狀态變量和通路方法的内容。我們将描述每一個元件,并讓您有機會看到它的實際應用。雖然 NIO 的内部統計機制初看起來可能很複雜,但是您很快就會看到大部分的實際工作都已經替您完成了。您可能習慣于通過手工編碼進行簿記—即使用位元組數組和索引變量,現在它已在 NIO 中内部地處理了。

可以用三個值指定緩沖區在任意時刻的狀态:

position

limit

capacity

參數

寫模式

讀模式

位置(position)

目前緩沖區的位置,将從position的下一個位置寫資料

目前緩沖區讀取的位置,将從此位置後,讀取資料

容量(capacity)

緩沖區的總量上限

上限(capacity)

緩沖區的實際上限,它總是小于或等于容量。通常情況下,和容量相等

代表可讀取的總容量,和上次寫入的資料量相等

這三個變量一起可以跟蹤緩沖區的狀态和它所包含的資料。我們将在下面的小節中詳細分析每一個變量,還要介紹它們如何适應典型的讀/寫(輸入/輸出)程序。在這個例子中,我們假定要将資料從一個輸入通道拷貝到一個輸出通道。

您可以回想一下,緩沖區實際上就是美化了的數組。在從通道讀取時,您将所讀取的資料放到底層的數組中。 position 變量跟蹤已經寫了多少資料。更準确地說,它指定了下一個位元組将放到數組的哪一個元素中。是以,如果您從通道中讀三個位元組到緩沖區中,那麼緩沖區的 position 将會設定為3,指向數組中第四個元素。

同樣,在寫入通道時,您是從緩沖區中擷取資料。 position 值跟蹤從緩沖區中擷取了多少資料。更準确地說,它指定下一個位元組來自數組的哪一個元素。是以如果從緩沖區寫了5個位元組到通道中,那麼緩沖區的position 将被設定為5,指向數組的第六個元素。

limit 變量表明還有多少資料需要取出(在從緩沖區寫入通道時),或者還有多少空間可以放入資料(在從通道讀入緩沖區時)。

position 總是小于或者等于 limit。

緩沖區的 capacity 表明可以儲存在緩沖區中的最大資料容量。實際上,它指定了底層數組的大小—或者至少是指定了準許我們使用的底層數組的容量。

limit 決不能大于 capacity。

我們首先觀察一個新建立的緩沖區。出于本例子的需要,我們假設這個緩沖區的總容量為8個位元組。 Buffer 的狀态如下所示:

<a href="http://s3.51cto.com/wyfs02/M00/26/F1/wKioL1NvoNjxs8bzAABFkApjjlM210.jpg" target="_blank"></a>

回想一下,limit 決不能大于 capacity,此例中這兩個值都被設定為 8。我們通過将它們指向數組的尾部之後(如果有第8個槽,則是第8個槽所在的位置)來說明這點。

<a href="http://s3.51cto.com/wyfs02/M01/26/F1/wKioL1NvoQaTagEKAAB9qBzbUew267.jpg" target="_blank"></a>

position 設定為0。如果我們讀一些資料到緩沖區中,那麼下一個讀取的資料就進入 slot 0 。如果我們從緩沖區寫一些資料,從緩沖區讀取的下一個位元組就來自 slot 0 。 position 設定如下所示:

<a href="http://s3.51cto.com/wyfs02/M00/26/F1/wKiom1NvoVbzcZMjAABcJhcgrKI309.jpg" target="_blank"></a>

由于 capacity 不會改變,是以我們在下面的讨論中可以忽略它。

現在我們可以開始在新建立的緩沖區上進行讀/寫操作。首先從輸入通道中讀一些資料到緩沖區中。第一次讀取得到三個位元組。它們被放到數組中從 position 開始的位置,這時 position 被設定為 0。讀完之後,position就增加到 3,如下所示:

<a href="http://s3.51cto.com/wyfs02/M02/26/F1/wKioL1NvoXrSwdTpAABVbZW9AwA525.jpg" target="_blank"></a>

limit 沒有改變。

在第二次讀取時,我們從輸入通道讀取另外兩個位元組到緩沖區中。這兩個位元組儲存在由 position 所指定的位置上, position 因而增加 2:

<a href="http://s3.51cto.com/wyfs02/M01/26/F1/wKiom1NvoaXBKan-AABVC8OJieE326.jpg" target="_blank"></a>

現在我們要将資料寫到輸出通道中。在這之前,我們必須調用 flip() 方法。這個方法做兩件非常重要的事:

1.它将 limit 設定為目前 position。

2.它将 position 設定為 0。

前一小節中的圖顯示了在flip 之前緩沖區的情況。下面是在flip 之後的緩沖區:

<a href="http://s3.51cto.com/wyfs02/M02/26/F1/wKioL1NvoiaB4G7kAABWRdAVm8M109.jpg" target="_blank"></a>

我們現在可以将資料從緩沖區寫入通道了。 position 被設定為 0,這意味着我們得到的下一個位元組是第一個位元組。 limit 已被設定為原來的 position,這意味着它包括以前讀到的所有位元組,并且一個位元組也不多。

在第一次寫入時,我們從緩沖區中取四個位元組并将它們寫入輸出通道。這使得 position 增加到 4,而 limit 不變,如下所示:

<a href="http://s3.51cto.com/wyfs02/M01/26/F1/wKiom1NvomCDS4mbAABVVTf3Fi8542.jpg" target="_blank"></a>

我們隻剩下一個位元組可寫了。limit在我們調用 flip() 時被設定為 5,并且 position 不能超過 limit。是以最後一次寫入操作從緩沖區取出一個位元組并将它寫入輸出通道。這使得position 增加到 5,并保持 limit 不變,如下所示:

<a href="http://s3.51cto.com/wyfs02/M00/26/F1/wKioL1NvokjCbyZlAABao4M-53I247.jpg" target="_blank"></a>

最後一步是調用緩沖區的clear() 方法。這個方法重設緩沖區以便接收更多的位元組。 Clear 做兩種非常重要的事情:

1.它将 limit 設定為與 capacity 相同。

2.它設定 position 為 0。

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

<a href="http://s3.51cto.com/wyfs02/M00/26/F1/wKiom1NvopexUZIqAABahg_gwN4510.jpg" target="_blank"></a>

緩沖區現在可以接收新的資料了。

對比rewind()、clear()和flip()函數

rewind()将position置零,并清除标志位(mark)。它的作用在于為提取Buffer的有效資料做準備。

clear()也将position置零,同時将limit設定為capacity的大小,并清除标志位(mark)。由于清空了limit,是以便無法的值Buffer内哪些資料是真實有效的。這個方法用于為重新寫Buffer做準備。

flip()先将limit設定到position所在的位置,然後将position置為零,并清出标志位mark。它通常在讀寫轉換時使用。

rewind()

clear()

flip()

置零

mark

清空

未改動

設定為capacity

設定為position

作用

為提取Buffer的有效資料做準備

為重新寫Buffer做準備

在讀寫轉換時使用

到目前為止,我們隻是使用緩沖區将資料從一個通道轉移到另一個通道。然而,程式經常需要直接處理資料。例如,您可能需要将使用者資料儲存到磁盤。在這種情況下,您必須将這些資料直接放入緩沖區,然後用通道将緩沖區寫入磁盤。

或者,您可能想要從磁盤讀取使用者資料。在這種情況下,您要将資料從通道讀到緩沖區中,然後檢查緩沖區中的資料。

在本節的最後,我們将詳細分析如何使用 ByteBuffer 類的 get() 和 put() 方法直接通路緩沖區中的資料。

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

byte get();

ByteBuffer get( byte dst[] );

ByteBuffer get( byte dst[], intoffset, int length );

byte get( int index );

第一個方法擷取單個位元組。第二和第三個方法将一組位元組讀到一個數組中。第四個方法從緩沖區中的特定位置擷取位元組。那些傳回 ByteBuffer 的方法隻是傳回調用它們的緩沖區的 this 值。

此外,我們認為前三個get() 方法是相對的,而最後一個方法是絕對的。相對意味着 get() 操作服從 limit 和 position 值—更明确地說,位元組是從目前 position 讀取的,而 position 在 get 之後會增加。另一方面,一個絕對方法會忽略 limit 和 position 值,也不會影響它們。事實上,它完全繞過了緩沖區的統計方法。

上面列出的方法對應于ByteBuffer 類。其他類有等價的get() 方法,這些方法除了不是處理位元組外,其它方面是是完全一樣的,它們處理的是與該緩沖區類相适應的類型。

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

ByteBuffer put( byte b );

ByteBuffer put( byte src[] );

ByteBuffer put( byte src[], intoffset, int length );

ByteBuffer put( ByteBuffer src);

ByteBuffer put( int index, byte b );

第一個方法寫入(put)單個位元組。第二和第三個方法寫入來自一個數組的一組位元組。第四個方法将資料從一個給定的源 ByteBuffer 寫入這個 ByteBuffer。第五個方法将位元組寫入緩沖區中特定的位置。那些傳回 ByteBuffer 的方法隻是傳回調用它們的緩沖區的 this 值。

與 get() 方法一樣,我們将把 put() 方法劃分為相對或者絕對的。前四個方法是相對的,而第五個方法是絕對的。

上面顯示的方法對應于ByteBuffer 類。其他類有等價的put() 方法,這些方法除了不是處理位元組之外,其它方面是完全一樣的。它們處理的是與該緩沖區類相适應的類型。

除了前些小節中描述的get() 和 put() 方法, ByteBuffer 還有用于讀寫不同類型的值的其他方法,如下所示:

getByte()

getChar()

getShort()

getInt()

getLong()

getFloat()

getDouble()

putByte()

putChar()

putShort()

putInt()

putLong()

putFloat()

putDouble()

事實上,這其中的每個方法都有兩種類型:一種是相對的,另一種是絕對的。它們對于讀取格式化的二進制資料(如圖像檔案的頭部)很有用。

您可以在例子程式TypesInByteBuffer.java 中看到這些方法的實際應用。

<code>// TypesInByteBuffer</code>

<code>import</code> <code>java.io.*;</code>

<code>import</code> <code>java.nio.*;</code>

<code>import</code> <code>java.nio.channels.*;</code>

<code>public</code> <code>class</code> <code>TypesInByteBuffer</code>

<code>{</code>

<code>  </code><code>static</code> <code>public</code> <code>void</code> <code>main( String args[] ) </code><code>throws</code> <code>Exception {</code>

<code>    </code><code>ByteBuffer buffer = ByteBuffer.allocate( </code><code>64</code> <code>);</code>

<code>    </code><code>buffer.putInt( </code><code>30</code> <code>);</code>

<code>    </code><code>buffer.putLong( 7000000000000L );</code>

<code>buffer.putDouble( Math.PI );</code>

<code>buffer.putInt( </code><code>40</code> <code>);</code>

<code>    </code><code>buffer.flip();</code>

<code>    </code><code>//按照順序擷取—相對位置</code>

<code>    </code><code>System.out.println( buffer.getInt() );</code>

<code>    </code><code>System.out.println( buffer.getLong() );</code>

<code>System.out.println( buffer.getDouble() );</code>

<code>System.out.println( buffer.getInt() );</code>

<code>//按照順序擷取—絕對位置,與上面的結果一樣</code>

<code>System.out.println( buffer.getInt(</code><code>0</code><code>));</code><code>//int類型為4byte</code>

<code>System.out.println( buffer.getLong(</code><code>4</code><code>) );</code><code>// long類型為4byte</code>

<code>System.out.println( buffer.getDouble(</code><code>12</code><code>) );</code><code>// double類型為4byte</code>

<code>System.out.println( buffer.getInt(</code><code>20</code><code>) );</code>

<code>  </code><code>}</code>

<code>}</code>

結果:

{"hb":[0,0,0,30,0,0,6,93,-48,-125,112,0,64,9,33,-5,84,68,45,24,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"offset":0,"isReadOnly":false,"bigEndian":true,"nativeByteOrder":false,"mark":-1,"position":24,"limit":64,"capacity":64,"address":0}

30

7000000000000

3.141592653589793

40

下面的内部循環概括了使用緩沖區将資料從輸入通道拷貝到輸出通道的過程。

<code>while</code> <code>(</code><code>true</code><code>) {</code>

<code>     </code><code>buffer.clear();</code>

<code>     </code><code>int</code> <code>r = fcin.read( buffer );</code>

<code>     </code><code>if</code> <code>(r==-</code><code>1</code><code>) {</code>

<code>       </code><code>break</code><code>;</code>

<code>     </code><code>}</code>

<code>     </code><code>buffer.flip();</code>

<code>     </code><code>fcout.write( buffer );</code>

read() 和 write() 調用得到了極大的簡化,因為許多工作細節都由緩沖區完成了。 clear() 和 flip() 方法用于讓緩沖區在讀和寫之間切換。

本文轉自 夢朝思夕 51CTO部落格,原文連結:http://blog.51cto.com/qiangmzsx/1409627

繼續閱讀