NIO——緩衝區Buffer

一、緩衝區
 緩衝區(Buffer)是一個用於存取特定基本數據類型數據的容器,底層是這些基本數據類型的數組,由java.nio包定義,所有緩衝區都是Buffer抽象類的子類。主要用於與NIO通道進行交互,數據是從通道讀入緩衝區,從緩衝區寫入通道中的。
 根據數據類型不同,NIO提供了以下類型的Buffer:
  ByteBuffer
  CharBuffer
  ShortBuffer
  IntBuffer
  LongBuffer
  FloatBuffer
  DoubleBuffer
 沒有提供BooleanBuffer,其中最常用的是ByteBuffer,這些Buffer的使用方式幾乎一樣

二、Buffer的基本屬性
 在緩衝區的抽象基類Buffer中定義了緩衝區的四個基本屬性:

public abstract class Buffer {
	private int mark = -1;
	private int position = 0;
	private int limit;
	private int capacity;
}

 ①容量 (capacity) :表示 Buffer 的最大數據容量,緩衝區容量不能爲負,並且創建後不能更改,其實就是指定底層數組的長度
 ② 限制 (limit):第一個不能讀取或寫入數據的索引位置,即位於 limit 及其之後的數組索引位置均不可讀寫。緩衝區的limit值不能爲負,並且不能大於其容量
 ③位置 (position):下一個要讀取或寫入數據的索引位置。緩衝區的位置不能爲負,並且不能大於其限制limit值
 ④標記 (mark) 與重置 (reset):標記的是一個索引位置,通過 Buffer 中的 mark() 方法指定 Buffer 中一個特定的 position,之後可以通過調用 reset() 方法恢復到這個 position 再次進行讀寫
 標記、位置、限制、容量遵守以下規則:
  0 <= mark <= position <= limit <= capacity

三、Buffer的數據存取
 Buffer通過兩個核心方法存取數據:put()和get()都有很多重載方法

put();//存入數據到緩衝區
get();//從緩衝區獲取數據

 1、Buffer數據存取的示例:以ByteBuffer爲例

public void testBuffer() {
	String str = "abcde";
	// 使用allocate()創建指定容量的buffer
	ByteBuffer buf = ByteBuffer.allocate(10);
	System.out.println(buf.capacity());// 10
	System.out.println(buf.limit());// 10
	System.out.println(buf.position());// 0
	// 向創建的buffer中存入數據
	buf.put(str.getBytes());
	System.out.println(buf.capacity());// 10
	System.out.println(buf.limit());// 10
	System.out.println(buf.position());// 5,創建的是byte類型的buffer,一個位置只能存放一個字符,因此position值變爲5
	buf.flip();// 執行該方法後limit值變爲原position值,position值變爲0,即指定可操作的範圍爲0-position,此處即讀的範圍爲0-5
	System.out.println(buf.capacity());// 10
	System.out.println(buf.limit());// 5
	System.out.println(buf.position());// 0
	byte[] dst = new byte[buf.limit()];
	buf.get(dst);// 使用get()將緩衝區中的數據寫入到指定的byte[]
	System.out.println(new String(dst, 0, dst.length));// abcde
	System.out.println(buf.capacity());// 10
	System.out.println(buf.limit());// 5
	System.out.println(buf.position());// 5
	buf.rewind();// 將position的值置爲0,可通過該方法再重頭開始讀數據
	System.out.println(buf.capacity());// 10
	System.out.println(buf.limit());// 5
	System.out.println(buf.position());// 0
	buf.clear();// 僅僅是將limit的值重置爲capacity,將position的值重置爲0,這樣就可以從頭開始寫數據,但並沒有清空buffer中的數據
	System.out.println(buf.capacity());// 10
	System.out.println(buf.limit());// 10
	System.out.println(buf.position());// 0
	System.out.println((char) buf.get());// a,buffer中的數據依然能夠讀取出來,get()是獲取當前position位置的值
}

在這裏插入圖片描述
 2、mark和reset的使用示例:

public void testBuffer1() {
	String str = "abcde";
	// 使用allocate()創建指定容量的buffer
	ByteBuffer buf = ByteBuffer.allocate(10);
	// 向創建的buffer中存入數據
	buf.put(str.getBytes());
	buf.flip();// limit值變爲原position值,position值變爲0
	byte[] dst = new byte[buf.limit()];
	buf.get(dst, 0, 2);// 從buffer中的當前position位置往後讀2個字符寫入dst中,寫入dst的起始索引爲0
	System.out.println(new String(dst, 0, 2));// ab
	System.out.println(buf.position());// 2
	buf.mark();// 記錄下此時position的位置:2
	buf.get(dst, 1, 2);//從buffer中的當前position位置往後讀2個字符寫入dst中,寫入dst的起始索引爲1
	System.out.println(new String(dst, 0, dst.length));//acd
	System.out.println(buf.position());// 4
	buf.reset();// 將position的值恢復至mark時的值
	System.out.println(buf.position());// 2
}

  注意:Buffer中的get方法的第2個參數offset指的是從目標數組的第offset個位置寫入數據到目標數組,第3個參數length指的則是從當前buffer的當前position位置向後讀取的buffer中的數據的長度,即offset和length針對的主體是不同的

public ByteBuffer get(byte[] dst, int offset, int length) {
	checkBounds(offset, length, dst.length);
	if (length > remaining())
		throw new BufferUnderflowException();
	int end = offset + length;
	for (int i = offset; i < end; i++)
		dst[i] = get();
	return this;
}

四、Buffer中的常用操作

方法 描述
Buffer clear() 將緩衝區的界限設置爲buffer的容量值,並將當前位置置爲 0,返回對緩衝區的引用,但並不清除緩衝區中的數據
Buffer flip() 將緩衝區的界限設置爲當前位置,並將當前位置充值爲 0
int capacity() 返回 Buffer 的 capacity 大小
boolean hasRemaining() 判斷緩衝區中是否還有元素
int limit() 返回 Buffer 的界限(limit) 的位置
Buffer limit(int n) 將設置緩衝區界限爲 n, 並返回一個具有新 limit 的緩衝區對象
Buffer mark() 對緩衝區設置標記
int position() 返回緩衝區的當前位置 position
Buffer position(int n) 將設置緩衝區的當前位置爲 n , 並返回修改後的 Buffer 對象
int remaining() 返回 position 和 limit 之間的元素個數
Buffer reset() 將位置 position 轉到以前設置的 mark 所在的位置
Buffer rewind() 將位置設置爲 0, 並取消設置的 mark
put(byte b) 將給定單個字節寫入緩衝區的當前位置
put(byte[] src) 從當前位置開始將 src 中的字節寫入緩衝區
put(int index, byte b) 將指定字節寫入緩衝區的指定索引位置(不會移動 position)
get() 讀取單個字節
get(byte[] dst) 批量讀取多個字節到 dst 中
get(int index) 讀取指定索引位置的字節(不會移動 position)

五、直接緩衝區和非直接緩衝區
 非直接緩衝區:通過allocate()方法分配的緩衝區,將緩衝區建立在JVM的內存中
 直接緩衝區:通過allocateDirect()方法分配的緩衝區,將緩衝區直接建立在物理內存中,可以提高數據操作的效率
 字節緩衝區要麼是直接的,要麼是非直接的。如果爲直接字節緩衝區,則 Java 虛擬機會盡最大努力直接在此緩衝區上執行本機 I/O 操作。也就是說,在每次調用基礎操作系統的一個本機 I/O 操作之前或之後, 虛擬機都會盡量避免將緩衝區的內容複製到中間緩衝區中(或從中間緩衝區中複製內容)。
 通過調用 allocateDirect() 方法來創建直接緩衝區,此方法返回的緩衝區在進行分配和取消分配時所需的成本通常高於創建非直接緩衝區。直接緩衝區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程序的內存需求量造成的影響可能並不明顯。所以,建議將直接緩衝區分配給那些易受基礎系統本機 I/O 操作影響的大型、持久的緩衝區。一般情況下,最好僅在直接緩衝區能在程序性能方面帶來明顯好處時分配它們。
 直接字節緩衝區還可以通過 FileChannel 的 map() 方法將文件區域直接映射到內存中來創建。該方法返回 MappedByteBuffer 。Java 平臺的實現有助於通過 JNI 從本機代碼創建直接字節緩衝區。如果以上這些緩衝區中的某個緩衝區實例指的是不可訪問的內存區域,則試圖訪問該區域不會更改該緩衝區的內容,並且將會在訪問期間或稍後的某個時間導致拋出不確定的異常。
 字節緩衝區是直接緩衝區還是非直接緩衝區可通過調用其 isDirect() 方法來判定。提供此方法是爲了能夠在性能關鍵型代碼中執行顯式的緩衝區類型管理。
 非直接緩衝區數據操作示意圖:
在這裏插入圖片描述
 讀數據時:應用程序先在JVM(即用戶地址空間)中創建一個緩衝區,然後JVM通知操作系統要讀取磁盤中的文件數據,同時操作系統需要在物理內存(即內核地址空間)中創建一個緩衝區用以接收從磁盤讀取的文件,在操作系統完成文件讀取後再將物理內存中的文件數據複製(copy)到JVM(用戶地址空間)的緩衝區中,應用程序在從JVM中讀取數據
 寫數據時:應用程序先在JVM(即用戶地址空間)中創建一個緩衝區,然後將數據寫入到JVM中的緩衝區,再通知操作系統將JVM中的數據寫入磁盤,這時操作系統需要在物理內存(即內核地址空間)中創建一個緩衝區用以存儲從JVM讀取的文件,在物理內存中的緩衝區創建好之後,再將JVM中的緩衝區中的數據複製到物理內存中的緩衝區,最後將物理內存中的緩衝區中的數據寫入磁盤
 可以看到,無論是讀還是寫數據,都存在複製數據的步驟,直接緩衝區就是應用程序通過直接將緩衝區建立在操作系統的物理內存上而將這一步驟省略了
 直接緩衝區數據操作示意圖:
在這裏插入圖片描述
 這樣在讀寫數據時,應用程序直接讀寫物理內存,物理內存中的數據直接寫入磁盤或者從磁盤接收數據。
 創建直接緩衝區:

public void testDirectBuffer() {
	//創建直接緩衝區
	ByteBuffer buf = ByteBuffer.allocateDirect(10);
	//判斷緩衝區是否爲直接緩衝區
	System.out.println(buf.isDirect());
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章