java NIO —— 緩衝區

一、緩衝區基礎 
概念上,緩衝區就像一個基本數據元素數組。
1、 屬性 
所有的緩衝區都具有四個屬性來提供關於其所包含的數據元素的信息。它們是: 
2、容量(Capacity) 
緩衝區能夠容納的數據元素的最大數量。這一容量在緩衝區創建時被設定,並且永遠不能被改變。 
3、上界(Limit) 
緩衝區的第一個不能被讀或寫的元素。或者說,緩衝區中現存元素的計數。 
4、位置(Position) 
下一個要被讀或寫的元素的索引。位置會自動由相應的get( )和put( )函數更新,每調用一次position會指向後一個元素。 
5、標記(Mark) 
一個備忘位置。調用 mark(  )來設定 mark = postion。調用 reset(  )設定 position = mark。標記在設定前是未定義的(undefined)。 
這四個屬性之間總是遵循以下關係: 
0 <= mark <= position <= limit <= capacity 

假設一個新創建的容量爲 10的 ByteBuffer 邏輯視圖如下:


容量是固定的,其他3個屬性可以在使用緩衝區時改變。

二、緩衝區API

1、父類Buffer:

public abstract class Buffer {  
        public final int capacity(  )  
        public final int position(  )  
        public final Buffer position (int newPosition)
        public final int limit(  )  
        public final Buffer limit (int newLimit)  
        public final Buffer mark(  )  
        public final Buffer reset(  )  
        public final Buffer clear(  )  
        public final Buffer flip(  )  
        public final Buffer rewind(  )  
        public final int remaining(  )  
        public final boolean hasRemaining(  )  
        public abstract boolean isReadOnly(  );  
}

2、子類ByteBuffer:

public abstract class ByteBuffer  
        extends Buffer implements Comparable  
{  
        // This is a partial API listing  
  
        public abstract byte get(  );  
        public abstract byte get (int index);  
        public abstract ByteBuffer put (byte b);  
        public abstract ByteBuffer put (int index, byte b);  

當調用get()和put()時,位置指針position會自動前進一位,但如果調用get(int)和put(int)時,位置指針不會自動前進一位。

注意:在對緩衝區操作時,都不會將緩衝區中的數據清空,即使是用clear(),也只是將指針還原到容器初創建狀態,你先用buffer.clear(),

然後再調用buffer.get(2),還是能取到值,操作方法都只是改變了position,limit等指針的位置而已。

例子:將hello字符串放入一個字節數組中

ByteBuffer buffer = ByteBuffer.allocate(10);

buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o'); 

調用5次put()後的緩衝區:

通過絕對方案放置元素:

buffer.put(0,(byte)'M')

buffer.put((byte)'w'); 

新的緩衝區圖如下:


3、翻轉flip()

我們現在要把上面的緩衝區中的數據取出來,如果現在在緩衝區上執行 get(),那麼它將從position位置處取數據,但這數據是未定義數據,取不到我們要的數據。

所以我們必須將位置值重新設爲 0,就會從正確位置開始獲取,但是它是怎樣知道何時到達我們所插入數據末端的呢?這就是上界屬性被引入的目的。上界屬性指明瞭緩衝區有效內容的末端。所以我們需要將上界屬性設置爲當前位置,然後將位置重置爲0。我們可以人工用下面的代碼實現:  

buffer.limit(buffer.position()).position(0);  

上面就是flip()方法了:

所以調用buffer.flip()後,緩衝區如下所以:


rewind()函數與 flip()相似,但不影響上界屬性。它只是將位置值設回 0。您可以使用 rewind()後退,重讀已經被翻轉的緩衝區中的數據。  
注意:不要將緩衝區翻轉兩次,它實際上會大小變爲 0。翻轉一次後再翻轉一次,會把上界設爲位置的值0,並把位置設爲 0。上界和位置都變成 0。

嘗試對緩衝區上位置和上界都爲 0 的 get()操作會導致 BufferUnderflowException 異常。而 put()則會導致BufferOverflowException 異常。  

4、釋放

布爾函數 hasRemaining()會在釋放緩衝區時告訴您是否已經達到緩衝區的上界。以下是一種將數據元素從緩衝區釋放到一個數組的方法:
for (int i = 0; buffer.hasRemaining(  ), i++) {  
        myByteArray [i] = buffer.get(  );  

作爲選擇,remaining()函數將告知您從當前位置到上界還剩餘的元素數目。您也可以通過下面的循環來釋放緩衝區。 
int count = buffer.remaining(  );   
for (int i = 0; i < count, i++) {  
        myByteArray [i] = buffer.get(  );  

5、壓縮

有時,您可能只想從緩衝區中釋放一部分數據,而不是全部,然後重新填充。爲了實現這一點,未讀的數據元素需要下移以使第一個元素索引爲 0。

這裏使用了compact()

假設被部分釋放的緩衝區如下:


調用buffer.compact()後,緩衝區如下:


6、標記

緩衝區的標記在 mark(  )函數被調用之前是未定義的,調用時標記被設爲當前位置的值。reset(  )函數將位置設爲當前的標記值。如果標記值未定義,調用 reset(  )將導致 InvalidMarkException 異常。一些緩衝區函數會拋棄已經設定的標記(rewind(  ),clear(  ),以及 flip(  )總是拋棄標記)。如果新設定的值比當前的標記小,調用limit( )或 position( )帶有索引參數的版本會拋棄標記。

buffer.position(2).mark().position(4); 


如果這個緩衝區現在被傳遞給一個通道,兩個字節(“ow”)將會被髮送,而位置會前進到 6。如果我們此時調用 reset(  ),位置將會被設爲標記。再次將緩衝區傳遞給通道將導致四個字節(“llow”)被髮送。 

7、比較

public abstract class ByteBuffer  
        extends Buffer implements Comparable  
{  
        // This is a partial API listing  
  
        public boolean equals (Object ob)  
        public int compareTo (Object ob)  

兩個緩衝區被認爲相等的充要條件是: 

(1)兩個對象類型相同。包含不同數據類型的 buffer 永遠不會相等,而且 buffer絕不會等於非buffer對象。 
(2)兩個對象都剩餘同樣數量的元素。Buffer 的容量不需要相同,而且緩衝區中剩餘數據的索引也不必相同。但每個緩衝區中剩餘元素的數目(從位置到上界)必須相同。 
(3)在每個緩衝區中應被Get()函數返回的剩餘數據元素序列必須一致。 

相等的緩衝區:


不相等的緩衝區:


8、批量移動

public abstract class CharBuffer  
        extends Buffer implements CharSequence, Comparable  
{  
        // This is a partial API listing  
  
        public CharBuffer get (char [] dst)  
        public CharBuffer get (char [] dst, int offset, int length)  
  
        public final CharBuffer put (char[] src)  
        public CharBuffer put (char [] src, int offset, int length)  
        public CharBuffer put (CharBuffer src)  
  
        public final CharBuffer put (String src)  
        public CharBuffer put (String src, int start, int end)  

public class BufferTest2 {
	public static void main(String[] args) {
		CharBuffer buffer = CharBuffer.allocate(100);
		String str = "zebiao";
		for(int i = 0; i < str.length(); i++){
			buffer.put(str.charAt(i));
		}
		char[] array2 = new char[]{'a','b','c','d','e'};
		buffer.put(array2,0,3);
		buffer.flip();
		char[] array = new char[20];
		int length = buffer.remaining();
		buffer.get(array, 0, length);
		for(char c : array){
			System.out.println(c);
		}
	}
}
輸出結果:

z
e
b
i
a
o
a
b
c

三、創建緩衝區

public abstract class CharBuffer  
        extends Buffer implements CharSequence, Comparable  
{  
        // This is a partial API listing  
  
        public static CharBuffer allocate (int capacity)  
  
        public static CharBuffer wrap (char [] array)  
        public static CharBuffer wrap (char [] array, int offset,   
  int length)  
  
        public final boolean hasArray(  )  
        public final char [] array(  )  
        public final int arrayOffset(  )  
}

 
CharBuffer charBuffer = CharBuffer.allocate (100); 

char [] myArray = new char [100]; 
CharBuffer charbuffer = CharBuffer.wrap (myArray); 

這段代碼構造了一個新的緩衝區對象,但數據元素會存在於數組中。這意味着通過調用put()函數造成的對緩衝區的改動會直接影響這個數組,而且對這個數組的任何改動也會對這個緩衝區對象可見。

帶有 offset 和 length 作爲參數的 wrap()函數版本則會構造一個按照您提供的offset和 length 參數值初始化位置和上界的緩衝區。這樣做: 
CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42); 創建了一個 position 值爲 12,limit 值爲 54,容量爲 myArray.length 的緩衝區。 
這個函數並不像您可能認爲的那樣,創建了一個只佔用了一個數組子集的緩衝區。這個緩衝區可以存取這個數組的全部範圍;offset 和 length 參數只是設置了初始的狀態。調用使用上面代碼中的方法創建的緩衝區中的 clear()函數,然後對其進行填充,直到超過上界值,這將會重寫數組中的所有元素。

四、 複製緩衝區 

public abstract class CharBuffer  
        extends Buffer implements CharSequence, Comparable  
{  
        // This is a partial API listing  
  
        public abstract CharBuffer duplicate(  );  
        public abstract CharBuffer asReadOnlyBuffer(  );  
        public abstract CharBuffer slice(  );  

 Duplicate()函數創建了一個與原始緩衝區相似的新緩衝區。兩個緩衝區共享數據元素,擁有同樣的容量,但每個緩衝區擁有各自的位置,上界和標記屬性。對一個緩衝區內的數據元素所做的改變會反映在另外一個緩衝區上。

public class BufferTest3 {
	public static void main(String[] args) {
		CharBuffer buffer = CharBuffer.allocate(10);
		char[] array = new char[]{'h','z','b','i','a','o'};
		buffer.put(array);
		buffer.flip();
		CharBuffer buffer2 = buffer.duplicate();
		buffer2.position(2);
		System.out.println(buffer2.get());
		System.out.println(buffer.get());
		
		buffer2.put(2,'x');
		System.out.println(buffer2.get(2));
		System.out.println(buffer.get(2));
	}
}
輸出結果:

b
h
x
x


分割緩衝區與複製相似,但 slice()創建一個從原始緩衝區的當前位置開始的新緩衝區,並且其容量是原始緩衝區的剩餘元素數量(limit-position)。這個新緩衝區與原始緩衝區共享一段數據元素子序列。

要創建一個映射到數組位置 12-20(9 個元素)的 buffer 對象,應使用下面的代碼實現: 
 
char [] myBuffer = new char [100];  
CharBuffer cb = CharBuffer.wrap (myBuffer);  
cb.position(12).limit(21);  
CharBuffer sliced = cb.slice(  ); 

public class BufferTest3 {
	public static void main(String[] args) {
		char[] array = new char[]{'h','z','b','i','a','o'};
		CharBuffer buffer = CharBuffer.wrap(array);
		buffer.position(1).limit(4);
		CharBuffer buffer2 = buffer.slice();
		while(buffer2.hasRemaining()){
			System.out.println(buffer2.get());
		}
		
		System.out.println("========");
		
		buffer2.put(2,'x');
		buffer2.flip();
		while(buffer2.hasRemaining()){
			System.out.println(buffer2.get());
		}
		System.out.println("========");
		
		buffer.position(0).limit(array.length);
		while(buffer.hasRemaining()){
			System.out.println(buffer.get());
		}

	}
}

輸出結果:

z

b
i
========
z
b
x
========
h
z
b
x
a
o

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