1. 程式人生 > >Buffer類的詳解(轉)

Buffer類的詳解(轉)

iteye 原始數據類型 pub bst exception 如何 數字 硬件 final

Buffer 類是 java.nio 的構造基礎。一個 Buffer 對象是固定數量的數據的容器,其作用是一個存儲器,或者分段運輸區,在這裏,數據可被存儲並在之後用於檢索。緩沖區可以被寫滿或釋放。對於每個非布爾原始數據類型都有一個緩沖區類,即 Buffer 的子類有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,是沒有 BooleanBuffer 之說的。盡管緩沖區作用於它們存儲的原始數據類型,但緩沖區十分傾向於處理字節。非字節緩沖區可以在後臺執行從字節或到字節的轉換,這取決於緩沖區是如何創建的。
◇ 緩沖區的四個屬性


所有的緩沖區都具有四個屬性來提供關於其所包含的數據元素的信息,這四個屬性盡管簡單,但其至關重要,需熟記於心:

  • 容量(Capacity):緩沖區能夠容納的數據元素的最大數量。這一容量在緩沖區創建時被設定,並且永遠不能被改變。
  • 上界(Limit):緩沖區的第一個不能被讀或寫的元素。緩沖創建時,limit 的值等於 capacity 的值。假設 capacity = 1024,我們在程序中設置了 limit = 512,說明,Buffer 的容量為 1024,但是從 512 之後既不能讀也不能寫,因此可以理解成,Buffer 的實際可用大小為 512。
  • 位置(Position):下一個要被讀或寫的元素的索引。位置會自動由相應的 get() 和 put() 函數更新。 這裏需要註意的是positon的位置是從0開始的。
  • 標記(Mark):一個備忘位置。標記在設定前是未定義的(undefined)。使用場景是,假設緩沖區中有 10 個元素,position 目前的位置為 2(也就是如果get的話是第三個元素),現在只想發送 6 - 10 之間的緩沖數據,此時我們可以 buffer.mark(buffer.position()),即把當前的 position 記入 mark 中,然後 buffer.postion(6),此時發送給 channel 的數據就是 6 - 10 的數據。發送完後,我們可以調用 buffer.reset() 使得 position = mark,因此這裏的 mark 只是用於臨時記錄一下位置用的。

請切記,在使用 Buffer 時,我們實際操作的就是這四個屬性的值。我們發現,Buffer 類並沒有包括 get() 或 put() 函數。但是,每一個Buffer 的子類都有這兩個函數,但它們所采用的參數類型,以及它們返回的數據類型,對每個子類來說都是唯一的,所以它們不能在頂層 Buffer 類中被抽象地聲明。它們的定義必須被特定類型的子類所遵從。若不加特殊說明,我們在下面討論的一些內容,都是以 ByteBuffer 為例,當然,它當然有 get() 和 put() 方法了。
◇ 相對存取和絕對存取

Java代碼

  1. public abstract class ByteBuffer extends Buffer implements Comparable {
  2. // This is a partial API listing
  3. public abstract byte get( );
  4. public abstract byte get (int index);
  5. public abstract ByteBuffer put (byte b);
  6. public abstract ByteBuffer put (int index, byte b);
  7. }

來看看上面的代碼,有不帶索引參數的方法和帶索引參數的方法。不帶索引的 get 和 put,這些調用執行完後,position 的值會自動前進。當然,對於 put,如果調用多次導致位置超出上界(註意,是 limit 而不是 capacity),則會拋出 BufferOverflowException 異常;對於 get,如果位置不小於上界(同樣是 limit 而不是 capacity),則會拋出 BufferUnderflowException 異常。這種不帶索引參數的方法,稱為相對存取,相對存取會自動影響緩沖區的位置屬性。帶索引參數的方法,稱為絕對存取,絕對存儲不會影響緩沖區的位置屬性,但如果你提供的索引值超出範圍(負數或不小於上界),也將拋出 IndexOutOfBoundsException 異常。
◇ 翻轉
我們把 hello 這個串通過 put 存入一 ByteBuffer 中,如下所示:將 hello 存入 ByteBuffer 中

Java代碼

  1. ByteBuffer buffer = ByteBuffer.allocate(1024);
  2. buffer.put((byte)‘H‘).put((byte)‘e‘).put((byte)‘l‘).put((byte)‘l‘).put((byte)‘o‘);

此時,position = 5,limit = capacity = 1024。現在我們要從正確的位置從 buffer 讀數據,我們可以把 position 置為 0,那麽字符串的結束位置在哪呢?這裏上界該出場了。如果把上界設置成當前 position 的位置,即 5,那麽 limit 就是結束的位置。上界屬性指明了緩沖區有效內容的末端。人工實現翻轉:

Java代碼

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

但這種從填充到釋放狀態的緩沖區翻轉是API設計者預先設計好的,他們為我們提供了一個非常便利的函數:buffer.flip()。另外,rewind() 函數與 flip() 相似,但不影響上界屬性,它只是將位置值設回 0。在進行buffer讀操作的時候,一般都會使用buffer.flip()函數。
◇ 釋放(Drain)
這裏的釋放,指的是緩沖區通過 put 填充數據後,然後被讀出的過程。上面講了,要讀數據,首先得翻轉。那麽怎麽讀呢?hasRemaining() 會在釋放緩沖區時告訴你是否已經達到緩沖區的上界:hasRemaining()函數和Remaining()函數有密切的功能,

Java代碼

  1. for (int i = 0; buffer.hasRemaining(); i++) {
  2. myByteArray[i] = buffer.get();
  3. }

很明顯,上面的代碼,每次都要判斷元素是否到達上界。我們可以做:改變後的釋放過程

Java代碼

  1. int count = buffer.hasRemaining();
  2. for (int i = 0; i < count; i++) {
  3. myByteArray[i] = buffer.get();
  4. }

第二段代碼看起來很高效,但請註意,緩沖區並不是多線程安全的。如果你想以多線程同時存取特定的緩沖區,你需要在存取緩沖區之前進行同步。因此,使用第二段代碼的前提是,你對緩沖區有專門的控制。
◇ buffer.clear()
clear() 函數將緩沖區重置為空狀態。它並不改變緩沖區中的任何數據元素,而是僅僅將 limit 設為容量的值,並把 position 設回 0。
◇ Compact(不知咋翻譯,壓縮?緊湊?)
有時候,我們只想釋放出一部分數據,即只讀取部分數據。當然,你可以把 postion 指向你要讀取的第一個數據的位置,將 limit 設置成最後一個元素的位置 + 1。但是,一旦緩沖區對象完成填充並釋放,它就可以被重新使用了。所以,緩沖區一旦被讀取出來,已經沒有使用價值了。
以 Mellow 為例,填充後為 Mellow,但如果我們僅僅想讀取 llow。讀取完後,緩沖區就可以重新使用了。Me 這兩個位置對於我們而言是沒用的。我們可以將 llow 復制至 0 - 3 上,Me 則被沖掉。但是 4 和 5 仍然為 o 和 w。這個事我們當然可以自行通過 get 和 put 來完成,但 api 給我們提供了一個 compact() 的函數,此函數比我們自己使用 get 和 put 要高效的多。

Compact 之前的緩沖區
buffer.compact() 會使緩沖區的狀態圖如下圖所示:

Compact 之後的緩沖區
這裏發生了幾件事:

  1. 數據元素 2 - 5 被復制到 0 - 3 位置,位置 4 和 5 不受影響,但現在正在或已經超出了當前位置,因此是“死的”。它們可以被之後的 put() 調用重寫。
  2. Position 已經被設為被復制的數據元素的數目,也就是說,緩沖區現在被定位在緩沖區中最後一個“存活”元素的後一個位置。
  3. 上界屬性被設置為容量的值,因此緩沖區可以被再次填滿。
  4. 調用 compact() 的作用是丟棄已經釋放的數據,保留未釋放的數據,並使緩沖區對重新填充容量準備就緒。該例子中,你當然可以將 Me 之前已經讀過,即已經被釋放過。


◇ 緩沖區的比較
有時候比較兩個緩沖區所包含的數據是很有必要的。所有的緩沖區都提供了一個常規的 equals() 函數用以測試兩個緩沖區的是否相等,以及一個 compareTo() 函數用以比較緩沖區。
兩個緩沖區被認為相等的充要條件是:

  • 兩個對象類型相同。包含不同數據類型的 buffer 永遠不會相等,而且buffer 絕不會等於非 buffer對象。
  • 兩個對象都剩余同樣數量(limit - position)的元素。Buffer 的容量不需要相同,而且緩沖區中剩余數據的索引也不必相同。
  • 在每個緩沖區中應被 get() 函數返回的剩余數據元素序列([position, limit - 1] 位置對應的元素序列)必須一致。


兩個被認為是相等的緩沖區

兩個被認為是不相等的緩沖區
緩沖區也支持用 compareTo() 函數以詞典順序進行比較,當然,這是所有的緩沖區實現了 java.lang.Comparable 語義化的接口。這也意味著緩沖區數組可以通過調用 java.util.Arrays.sort() 函數按照它們的內容進行排序。
與 equals() 相似,compareTo() 不允許不同對象間進行比較。但 compareTo()更為嚴格:如果你傳遞一個類型錯誤的對象,它會拋出 ClassCastException 異常,但 equals() 只會返回 false。
比較是針對每個緩沖區你剩余數據(從 position 到 limit)進行的,與它們在 equals() 中的方式相同,直到不相等的元素被發現或者到達緩沖區的上界。如果一個緩沖區在不相等元素發現前已經被耗盡,較短的緩沖區被認為是小於較長的緩沖區。這裏有個順序問題:下面小於零的結果(表達式的值為 true)的含義是 buffer2 < buffer1。切記,這代表的並不是 buffer1 < buffer2。

Java代碼

  1. if (buffer1.compareTo(buffer2) < 0) {
  2. // do sth, it means buffer2 < buffer1,not buffer1 < buffer2
  3. doSth();
  4. }

◇ 批量移動
緩沖區的設計目的就是為了能夠高效傳輸數據,一次移動一個數據元素並不高效。如你在下面的程序清單中所看到的那樣,buffer API 提供了向緩沖區你外批量移動數據元素的函數:

Java代碼

  1. public abstract class ByteBuffer extends Buffer implements Comparable {
  2. public ByteBuffer get(byte[] dst);
  3. public ByteBuffer get(byte[] dst, int offset, int length);
  4. public final ByteBuffer put(byte[] src);
  5. public ByteBuffer put(byte[] src, int offset, int length);
  6. }

如你在上面的程序清單中所看到的那樣,buffer API 提供了向緩沖區內外批量移動數據元素的函數。以 get 為例,它將緩沖區中的內容復制到指定的數組中,當然是從 position 開始咯。第二種形式使用 offset 和 length 參數來指定復制到目標數組的子區間。這些批量移動的合成效果與前文所討論的循環是相同的,但是這些方法可能高效得多,因為這種緩沖區實現能夠利用本地代碼或其他的優化來移動數據。
批量移動總是具有指定的長度。也就是說,你總是要求移動固定數量的數據元素。因此,get(dist) 和 get(dist, 0, dist.length) 是等價的。
對於以下幾種情況的數據復制會發生異常:

  • 如果你所要求的數量的數據不能被傳送,那麽不會有數據被傳遞,緩沖區的狀態保持不變,同時拋出BufferUnderflowException異常。
  • 如果緩沖區中的數據不夠完全填滿數組,你會得到一個異常。這意味著如果你想將一個小型緩沖區傳入一個大型數組,你需要明確地指定緩沖區中剩余的數據長度。

如果緩沖區存有比數組能容納的數量更多的數據,你可以重復利用如下代碼進行讀取:

Java代碼

  1. byte[] smallArray = new Byte[10];
  2. while (buffer.hasRemaining()) {
  3. int length = Math.min(buffer.remaining(), smallArray.length);
  4. buffer.get(smallArray, 0, length);
  5. // 每取出一部分數據後,即調用 processData 方法,length 表示實際上取到了多少字節的數據
  6. processData(smallArray, length);
  7. }

put() 的批量版本工作方式相似,只不過它是將數組裏的元素寫入 buffer 中而已,這裏不再贅述。
◇ 創建緩沖區
Buffer 的七種子類,沒有一種能夠直接實例化,它們都是抽象類,但是都包含靜態工廠方法來創建相應類的新實例。這部分討論中,將以 CharBuffer 類為例,對於其它六種主要的緩沖區類也是適用的。下面是創建一個緩沖區的關鍵函數,對所有的緩沖區類通用(要按照需要替換類名):

Java代碼

  1. public abstract class CharBuffer extends Buffer implements CharSequence, Comparable {
  2. // This is a partial API listing
  3. public static CharBuffer allocate (int capacity);
  4. public static CharBuffer wrap (char [] array);
  5. public static CharBuffer wrap (char [] array, int offset, int length);
  6. public final boolean hasArray();
  7. public final char [] array();
  8. public final int arrayOffset();
  9. }

新的緩沖區是由分配(allocate)或包裝(wrap)操作創建的。分配(allocate)操作創建一個緩沖區對象並分配一個私有的空間來儲存容量大小的數據元素。包裝(wrap)操作創建一個緩沖區對象但是不分配任何空間來儲存數據元素。它使用你所提供的數組作為存儲空間來儲存緩沖區中的數據元素。demos:

Java代碼

  1. // 這段代碼隱含地從堆空間中分配了一個 char 型數組作為備份存儲器來儲存 100 個 char 變量。
  2. CharBuffer charBuffer = CharBuffer.allocate (100);
  3. /**
  4. * 這段代碼構造了一個新的緩沖區對象,但數據元素會存在於數組中。這意味著通過調用 put() 函數造成的對緩
  5. * 沖區的改動會直接影響這個數組,而且對這個數組的任何改動也會對這個緩沖區對象可見。
  6. */
  7. char [] myArray = new char [100];
  8. CharBuffer charbuffer = CharBuffer.wrap (myArray);
  9. /**
  10. * 帶有 offset 和 length 作為參數的 wrap() 函數版本則會構造一個按照你提供的 offset 和 length 參
  11. * 數值初始化 position 和 limit 的緩沖區。
  12. *
  13. * 這個函數並不像你可能認為的那樣,創建了一個只占用了一個數組子集的緩沖區。這個緩沖區可以存取這個數組
  14. * 的全部範圍;offset 和 length 參數只是設置了初始的狀態。調用 clear() 函數,然後對其進行填充,
  15. * 直到超過 limit,這將會重寫數組中的所有元素。
  16. *
  17. * slice() 函數可以提供一個只占用備份數組一部分的緩沖區。
  18. *
  19. * 下面的代碼創建了一個 position 值為 12,limit 值為 54,容量為 myArray.length 的緩沖區。
  20. */
  21. CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42);

通過 allocate() 或者 wrap() 函數創建的緩沖區通常都是間接的。間接的緩沖區使用備份數組,你可以通過上面列出的 api 函數獲得對這些數組的存取權。
boolean 型函數 hasArray() 告訴你這個緩沖區是否有一個可存取的備份數組。如果這個函數的返回 true,array() 函數會返回這個緩沖區對象所使用的數組存儲空間的引用。如果 hasArray() 函數返回 false,不要調用 array() 函數或者 arrayOffset() 函數。如果你這樣做了你會得到一個 UnsupportedOperationException 異常。
如果一個緩沖區是只讀的,它的備份數組將會是超出 limit 的,即使一個數組對象被提供給 wrap() 函數。調用 array() 函數或 arrayOffset() 會拋出一個 ReadOnlyBufferException 異常以阻止你得到存取權來修改只讀緩沖區的內容。如果你通過其它的方式獲得了對備份數組的存取權限,對這個數組的修改也會直接影響到這個只讀緩沖區。
arrayOffset(),返回緩沖區數據在數組中存儲的開始位置的偏移量(從數組頭 0 開始計算)。如果你使用了帶有三個參數的版本的 wrap() 函數來創建一個緩沖區,對於這個緩沖區,arrayOffset() 會一直返回 0。不理解嗎?offset 和 length 只是指示了當前的 position 和 limit,是一個瞬間值,可以通過 clear() 來從 0 重新存數據,所以 arrayOffset() 返回的是 0。當然,如果你切分(slice() 函數)了由一個數組提供存儲的緩沖區,得到的緩沖區可能會有一個非 0 的數組偏移量。
◇ 復制緩沖區
緩沖區不限於管理數組中的外部數據,它們也能管理其他緩沖區中的外部數據。當一個管理其他緩沖器所包含的數據元素的緩沖器被創建時,這個緩沖器被稱為視圖緩沖器。
視圖存儲器總是通過調用已存在的存儲器實例中的函數來創建。使用已存在的存儲器實例中的工廠方法意味著視圖對象為原始存儲器的你部實現細節私有。數據元素可以直接存取,無論它們是存儲在數組中還是以一些其他的方式,而不需經過原始緩沖區對象的 get()/put() API。如果原始緩沖區是直接緩沖區,該緩沖區(視圖緩沖區)的視圖會具有同樣的效率優勢。
繼續以 CharBuffer 為例,但同樣的操作可被用於任何基本的緩沖區類型。用於復制緩沖區的 api:

Java代碼

  1. public abstract class CharBuffer extends Buffer implements CharSequence, Comparable {
  2. // This is a partial API listing
  3. public abstract CharBuffer duplicate();
  4. public abstract CharBuffer asReadOnlyBuffer();
  5. public abstract CharBuffer slice();
  6. }

● duplidate()
復制一個緩沖區會創建一個新的 Buffer 對象,但並不復制數據。原始緩沖區和副本都會操作同樣的數據元素。
duplicate() 函數創建了一個與原始緩沖區相似的新緩沖區。兩個緩沖區共享數據元素,擁有同樣的容量,但每個緩沖區擁有各自的 position、limit 和 mark 屬性。對一個緩沖區你的數據元素所做的改變會反映在另外一個緩沖區上。這一副本緩沖區具有與原始緩沖區同樣的數據視圖。如果原始的緩沖區為只讀,或者為直接緩沖區,新的緩沖區將繼承這些屬性。duplicate() 復制緩沖區:

Java代碼

  1. CharBuffer buffer = CharBuffer.allocate(8);
  2. buffer.position(3).limit(6).mark().position (5);
  3. CharBuffer dupeBuffer = buffer.duplicate();
  4. buffer.clear();


復制一個緩沖區
● asReadOnlyBuffer()
asReadOnlyBuffer() 函數來生成一個只讀的緩沖區視圖。這與duplicate() 相同,除了這個新的緩沖區不允許使用 put(),並且其 isReadOnly() 函數將會返回 true。
如果一個只讀的緩沖區與一個可寫的緩沖區共享數據,或者有包裝好的備份數組,那麽對這個可寫的緩沖區或直接對這個數組的改變將反映在所有關聯的緩沖區上,包括只讀緩沖區。
● slice()
分割緩沖區與復制相似,但 slice() 創建一個從原始緩沖區的當前 position 開始的新緩沖區,並且其容量是原始緩沖區的剩余元素數量(limit - position)。這個新緩沖區與原始緩沖區共享一段數據元素子序列。分割出來的緩沖區也會繼承只讀和直接屬性。slice() 分割緩沖區:

Java代碼

  1. CharBuffer buffer = CharBuffer.allocate(8);
  2. buffer.position(3).limit(5);
  3. CharBuffer sliceBuffer = buffer.slice();


創建分割緩沖區
◇ 字節緩沖區(ByteBuffer)
ByteBuffer 只是 Buffer 的一個子類,但字節緩沖區有字節的獨特之處。字節緩沖區跟其他緩沖區類型最明顯的不同在於,它可以成為通道所執行的 I/O 的源頭或目標,後面你會發現通道只接收 ByteBuffer 作為參數。
字節是操作系統及其 I/O 設備使用的基本數據類型。當在 JVM 和操作系統間傳遞數據時,將其他的數據類型拆分成構成它們的字節是十分必要的,系統層次的 I/O 面向字節的性質可以在整個緩沖區的設計以及它們互相配合的服務中感受到。同時,操作系統是在內存區域中進行 I/O 操作。這些內存區域,就操作系統方面而言,是相連的字節序列。於是,毫無疑問,只有字節緩沖區有資格參與 I/O 操作。
非字節類型的基本類型,除了布爾型都是由組合在一起的幾個字節組成的。那麽必然要引出另外一個問題:字節順序。
多字節數值被存儲在內存中的方式一般被稱為 endian-ness(字節順序)。如果數字數值的最高字節 - big end(大端),位於低位地址(即 big end 先寫入內存,先寫入的內存的地址是低位的,後寫入內存的地址是高位的),那麽系統就是大端字節順序。如果最低字節最先保存在內存中,那麽系統就是小端字節順序。在 java.nio 中,字節順序由 ByteOrder 類封裝:

Java代碼

  1. package java.nio;
  2. public final class ByteOrder {
  3. public static final ByteOrder BIG_ENDIAN;
  4. public static final ByteOrder LITTLE_ENDIAN;
  5. public static ByteOrder nativeOrder();
  6. public String toString();
  7. }

ByteOrder 類定義了決定從緩沖區中存儲或檢索多字節數值時使用哪一字節順序的常量。如果你需要知道 JVM 運行的硬件平臺的固有字節順序,請調用靜態類函數 nativeOrder()。
每個緩沖區類都具有一個能夠通過調用 order() 查詢的當前字節順序:

Java代碼

  1. public abstract class CharBuffer extends Buffer implements Comparable, CharSequence {
  2. // This is a partial API listing
  3. public final ByteOrder order();
  4. }

這個函數從 ByteOrder 返回兩個常量之一。對於除了 ByteBuffer 之外的其他緩沖區類,字節順序是一個只讀屬性,並且可能根據緩沖區的建立方式而采用不同的值。除了 ByteBuffer,其他通過 allocate() 或 wrap() 一個數組所創建的緩沖區將從 order() 返回與 ByteOrder.nativeOrder() 相同的數值。這是因為包含在緩沖區中的元素在 JVM 中將會被作為基本數據直接存取。
ByteBuffer 類有所不同:默認字節順序總是 ByteBuffer.BIG_ENDIAN,無論系統的固有字節順序是什麽。Java 的默認字節順序是大端字節順序,這允許類文件等以及串行化的對象可以在任何 JVM 中工作。如果固有硬件字節順序是小端,這會有性能隱患。在使用固有硬件字節順序時,將 ByteBuffer 的內容當作其他數據類型存取很可能高效得多。
為什麽 ByteBuffer 類需要一個字節順序?字節不就是字節嗎?ByteBuffer 對象像其他基本數據類型一樣,具有大量便利的函數用於獲取和存放緩沖區內容。這些函數對字節進行編碼或解碼的方式取決於 ByteBuffer 當前字節順序的設定。ByteBuffer 的字節順序可以隨時通過調用以 ByteOrder.BIG_ENDIAN 或 ByteOrder.LITTL_ENDIAN 為參數的 order() 函數來改變:

Java代碼

  1. public abstract class ByteBuffer extends Buffer implements Comparable {
  2. // This is a partial API listing
  3. public final ByteOrder order();
  4. public final ByteBuffer order(ByteOrder bo);
  5. }

如果一個緩沖區被創建為一個 ByteBuffer 對象的視圖,,那麽 order() 返回的數值就是視圖被創建時其創建源頭的 ByteBuffer 的字節順序。視圖的字節順序設定在創建後不能被改變,而且如果原始的字節緩沖區的字節順序在之後被改變,它也不會受到影響。
◇ 直接緩沖區
內核空間(與之相對的是用戶空間,如 JVM)是操作系統所在區域,它能與設備控制器(硬件)通訊,控制著用戶區域進程(如 JVM)的運行狀態。最重要的是,所有的 I/O 都直接(物理內存)或間接(虛擬內存)通過內核空間。
當進程(如 JVM)請求 I/O 操作的時候,它執行一個系統調用將控制權移交給內核。當內核以這種方式被調用,它隨即采取任何必要步驟,找到進程所需數據,並把數據傳送到用戶空間你的指定緩沖區。內核試圖對數據進行高速緩存或預讀取,因此進程所需數據可能已經在內核空間裏了。如果是這樣,該數據只需簡單地拷貝出來即可。如果數據不在內核空間,則進程被掛起,內核著手把數據讀進內存。

I/O 緩沖區操作簡圖
從圖中你可能會覺得,把數據從內核空間拷貝到用戶空間似乎有些多余。為什麽不直接讓磁盤控制器把數據送到用戶空間的緩沖區呢?首先,硬件通常不能直接訪問用戶空間。其次,像磁盤這樣基於塊存儲的硬件設備操作的是固定大小的數據塊,而用戶進程請求的可能是任意大小的或非對齊的數據塊。在數據往來於用戶空間與存儲設備的過程中,內核負責數據的分解、再組合工作,因此充當著中間人的角色。
因此,操作系統是在內存區域中進行 I/O 操作。這些內存區域,就操作系統方面而言,是相連的字節序列,這也意味著I/O操作的目標內存區域必須是連續的字節序列。在 JVM中,字節數組可能不會在內存中連續存儲(因為 JAVA 有 GC 機制),或者無用存儲單元(會被垃圾回收)收集可能隨時對其進行移動。
出於這個原因,引入了直接緩沖區的概念。直接字節緩沖區通常是 I/O 操作最好的選擇。非直接字節緩沖區(即通過 allocate() 或 wrap() 創建的緩沖區)可以被傳遞給通道,但是這樣可能導致性能損耗。通常非直接緩沖不可能成為一個本地 I/O 操作的目標。
如果你向一個通道中傳遞一個非直接 ByteBuffer 對象用於寫入,通道可能會在每次調用中隱含地進行下面的操作:

  1. 創建一個臨時的直接 ByteBuffer 對象。
  2. 將非直接緩沖區的內容復制到臨時直接緩沖區中。
  3. 使用臨時直接緩沖區執行低層 I/O 操作。
  4. 臨時直接緩沖區對象離開作用域,並最終成為被回收的無用數據。

這可能導致緩沖區在每個 I/O 上復制並產生大量對象,而這種事都是我們極力避免的。如果你僅僅為一次使用而創建了一個緩沖區,區別並不是很明顯。另一方面,如果你將在一段高性能腳本中重復使用緩沖區,分配直接緩沖區並重新使用它們會使你遊刃有余。
直接緩沖區可能比創建非直接緩沖區要花費更高的成本,它使用的內存是通過調用本地操作系統方面的代碼分配的,繞過了標準 JVM 堆棧,不受垃圾回收支配,因為它們位於標準 JVM 堆棧之外。
直接 ByteBuffer 是通過調用具有所需容量的 ByteBuffer.allocateDirect() 函數產生的。註意,wrap() 函數所創建的被包裝的緩沖區總是非直接的。與直接緩沖區相關的 api:

Java代碼

  1. public abstract class ByteBuffer extends Buffer implements Comparable {
  2. // This is a partial API listing
  3. public static ByteBuffer allocateDirect (int capacity);
  4. public abstract boolean isDirect();
  5. }

所有的緩沖區都提供了一個叫做 isDirect() 的 boolean 函數,來測試特定緩沖區是否為直接緩沖區。但是,ByteBuffer 是唯一可以被分配成直接緩沖區的 Buffer。盡管如此,如果基礎緩沖區是一個直接 ByteBuffer,對於非字節視圖緩沖區,isDirect() 可以是 true。
◇ 視圖緩沖區
I/O 基本上可以歸結成組字節數據的四處傳遞,在進行大數據量的 I/O 操作時,很又可能你會使用各種 ByteBuffer 類去讀取文件內容,接收來自網絡連接的數據,等等。ByteBuffer 類提供了豐富的 API 來創建視圖緩沖區。
視圖緩沖區通過已存在的緩沖區對象實例的工廠方法來創建。這種視圖對象維護它自己的屬性,容量,位置,上界和標記,但是和原來的緩沖區共享數據元素。
每一個工廠方法都在原有的 ByteBuffer 對象上創建一個視圖緩沖區。調用其中的任何一個方法都會創建對應的緩沖區類型,這個緩沖區是基礎緩沖區的一個切分,由基礎緩沖區的位置和上界決定。新的緩沖區的容量是字節緩沖區中存在的元素數量除以視圖類型中組成一個數據類型的字節數,在切分中任一個超過上界的元素對於這個視圖緩沖區都是不可見的。視圖緩沖區的第一個元素從創建它的 ByteBuffer 對象的位置開始(positon() 函數的返回值)。來自 ByteBuffer 創建視圖緩沖區的工廠方法:

Java代碼

  1. public abstract class ByteBuffer extends Buffer implements Comparable {
  2. // This is a partial API listing
  3. public abstract CharBuffer asCharBuffer();
  4. public abstract CharBuffer asShortBuffer( );
  5. public abstract CharBuffer asIntBuffer( );
  6. public abstract CharBuffer asLongBuffer( );
  7. public abstract CharBuffer asFloatBuffer( );
  8. public abstract CharBuffer asDoubleBuffer( );
  9. }

下面的代碼創建了一個 ByteBuffer 緩沖區的 CharBuffer 視圖。演示 7 個字節的 ByteBuffer 的 CharBuffer 視圖:

Java代碼

  1. /**
  2. * 1 char = 2 byte,因此 7 個字節的 ByteBuffer 最終只會產生 capacity 為 3 的 CharBuffer。
  3. *
  4. * 無論何時一個視圖緩沖區存取一個 ByteBuffer 的基礎字節,這些字節都會根據這個視圖緩沖區的字節順序設
  5. * 定被包裝成一個數據元素。當一個視圖緩沖區被創建時,視圖創建的同時它也繼承了基礎 ByteBuffer 對象的
  6. * 字節順序設定,這個視圖的字節排序不能再被修改。字節順序設定決定了這些字節對是怎麽樣被組合成字符
  7. * 型變量的,這樣可以理解為什麽 ByteBuffer 有字節順序的概念了吧。
  8. */
  9. ByteBuffer byteBuffer = ByteBuffer.allocate (7).order (ByteOrder.BIG_ENDIAN);
  10. CharBuffer charBuffer = byteBuffer.asCharBuffer();


7 個 字節的 ByteBuffer 的 CharBuffer 視圖
◇ 數據元素視圖
ByteBuffer 類為每一種原始數據類型提供了存取的和轉化的方法:

Java代碼

  1. public abstract class ByteBuffer extends Buffer implements Comparable {
  2. public abstract short getShort( );
  3. public abstract short getShort(int index);
  4. public abstract short getInt( );
  5. public abstract short getInt(int index);
  6. ......
  7. public abstract ByteBuffer putShort(short value);
  8. public abstract ByteBuffer putShort(int index, short value);
  9. public abstract ByteBuffer putInt(int value);
  10. public abstract ByteBuffer putInt(int index, int value);
  11. .......
  12. }

這些函數從當前位置開始存取 ByteBuffer 的字節數據,就好像一個數據元素被存儲在那裏一樣。根據這個緩沖區的當前的有效的字節順序,這些字節數據會被排列或打亂成需要的原始數據類型。
如果 getInt() 函數被調用,從當前的位置開始的四個字節會被包裝成一個 int 類型的變量然後作為函數的返回值返回。實際的返回值取決於緩沖區的當前的比特排序(byte-order)設置。不同字節順序取得的值是不同的:

Java代碼

  1. // 大端順序
  2. int value = buffer.order(ByteOrder.BIG_ENDIAN).getInt();
  3. // 小端順序
  4. int value = buffer.order(ByteOrder.LITTLE_ENDIAN).getInt();
  5. // 上述兩種方法取得的 int 是不一樣的,因此在調用此類方法前,請確保字節順序是你所期望的

如果你試圖獲取的原始類型需要比緩沖區中存在的字節數更多的字節,會拋出 BufferUnderflowException。

原文鏈接:http://www.cnblogs.com/lxzh/archive/2013/05/10/3071680.html

Buffer類的詳解(轉)