天天看點

NIO - Buffer

[color=#345286] Buffer 類是 java.nio 的構造基礎。一個 Buffer 對象是固定數量的資料的容器,其作用是一個存儲器,或者分段運輸區,在這裡,資料可被存儲并在之後用于檢索。緩沖區可以被寫滿或釋放。對于每個非布爾原始資料類型都有一個緩沖區類,即 [b]Buffer 的子類有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer[/b],是沒有 BooleanBuffer 之說的。盡管緩沖區作用于它們存儲的原始資料類型,但緩沖區十分傾向于處理位元組。非位元組緩沖區可以在背景執行從位元組或到位元組的轉換,這取決于緩沖區是如何建立的。[/color]

[color=#FF0000] [b][size=medium]◇ 緩沖區的四個屬性[/size][/b][/color]

[color=#345286] 所有的緩沖區都具有四個屬性來提供關于其所包含的資料元素的資訊,這四個屬性盡管簡單,但其至關重要,需熟記于心:[/color]

[list]

[*] [color=#345286][b]容量(Capacity):[/b]緩沖區能夠容納的資料元素的最大數量。這一容量在緩沖區建立時被設定,并且永遠不能被改變。[/color]

[*] [color=#345286][b]上界(Limit):[/b]緩沖區的第一個不能被讀或寫的元素。緩沖建立時,limit 的值等于 capacity 的值。假設 capacity = 1024,我們在程式中設定了 limit = 512,說明,Buffer 的容量為 1024,但是從 512 之後既不能讀也不能寫,是以可以了解成,Buffer 的實際可用大小為 512。[/color]

[*] [color=#345286][b]位置(Position):[/b]下一個要被讀或寫的元素的索引。位置會自動由相應的 get() 和 put() 函數更新。[/color]

[*] [color=#345286][b]标記(Mark):[/b]一個備忘位置。标記在設定前是未定義的(undefined)。使用場景是,假設緩沖區中有 10 個元素,position 目前的位置為 2,現在隻想發送 6 - 10 之間的緩沖資料,此時我們可以 buffer.mark(buffer.position()),即把目前的 position 記入 mark 中,然後 buffer.postion(6),此時發送給 channel 的資料就是 6 - 10 的資料。發送完後,我們可以調用 buffer.reset() 使得 position = mark,是以這裡的 mark 隻是用于臨時記錄一下位置用的。[/color]

[/list][color=#345286] [b]請切記,在使用 Buffer 時,我們實際操作的就是這四個屬性的值。[/b]我們發現,Buffer 類并沒有包括 get() 或 put() 函數。但是,每一個Buffer 的子類都有這兩個函數,但它們所采用的參數類型,以及它們傳回的資料類型,對每個子類來說都是唯一的,是以它們不能在頂層 Buffer 類中被抽象地聲明。它們的定義必須被特定類型的子類所遵從。若不加特殊說明,我們在下面讨論的一些内容,都是以 ByteBuffer 為例,當然,它當然有 get() 和 put() 方法了。[/color]

[color=#FF0000] [b][size=medium]◇ 相對存取和絕對存取[/size][/b][/color]

[color=#345286] 來看看上面的代碼,有不帶索引參數的方法和帶索引參數的方法。不帶索引的 get 和 put,這些調用執行完後,position 的值會自動前進。當然,對于 put,如果調用多次導緻位置超出上界(注意,是 limit 而不是 capacity),則會抛出 BufferOverflowException 異常;對于 get,如果位置不小于上界(同樣是 limit 而不是 capacity),則會抛出 BufferUnderflowException 異常。這種不帶索引參數的方法,稱為相對存取,相對存取會自動影響緩沖區的位置屬性。帶索引參數的方法,稱為絕對存取,絕對存儲不會影響緩沖區的位置屬性,但如果你提供的索引值超出範圍(負數或不小于上界),也将抛出 IndexOutOfBoundsException 異常。[/color]

[color=#FF0000] [b][size=medium]◇ 翻轉[/size][/b][/color]

[color=#345286] 我們把 hello 這個串通過 put 存入一 ByteBuffer 中,如下所示:[/color][color=#008000][b]将 hello 存入 ByteBuffer 中[/b][/color]

[color=#345286] 此時,position = 6,limit = capacity = 1024。現在我們要從正确的位置從 buffer 讀資料,我們可以把 position 置為 0,那麼字元串的結束位置在哪呢?這裡上界該出場了。如果把上界設定成目前 position 的位置,即 6,那麼 limit 就是結束的位置。上界屬性指明了緩沖區有效内容的末端。[/color][color=#008000][b]人工實作翻轉:[/b][/color]

[color=#345286] 但這種從填充到釋放狀态的緩沖區翻轉是API設計者預先設計好的,他們為我們提供了一個非常便利的函數:buffer.flip()。另外,rewind() 函數與 flip() 相似,但不影響上界屬性,它隻是将位置值設回 0。[/color]

[color=#FF0000] [b][size=medium]◇ 釋放(Drain)[/size][/b][/color]

[color=#345286] 這裡的釋放,指的是緩沖區通過 put 填充資料後,然後被讀出的過程。上面講了,要讀資料,首先得翻轉。那麼怎麼讀呢?[/color][color=#008000][b]hasRemaining() 會在釋放緩沖區時告訴你是否已經達到緩沖區的上界:[/b][/color]

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

[color=#345286] 第二段代碼看起來很高效,但請注意,緩沖區并不是多線程安全的。如果你想以多線程同時存取特定的緩沖區,你需要在存取緩沖區之前進行同步。是以,使用第二段代碼的前提是,你對緩沖區有專門的控制。[/color]

[color=#FF0000] [b][size=medium]◇ buffer.clear()[/size][/b][/color]

[color=#345286] clear() 函數将緩沖區重置為空狀态。它并不改變緩沖區中的任何資料元素,而是僅僅将 limit 設為容量的值,并把 position 設回 0。[/color]

[color=#FF0000] [b][size=medium]◇ Compact(不知咋翻譯,壓縮?緊湊?)[/size][/b][/color]

[color=#345286] 有時候,我們隻想釋放出一部分資料,即隻讀取部分資料。當然,你可以把 postion 指向你要讀取的第一個資料的位置,将 limit 設定成最後一個元素的位置 + 1。但是,一旦緩沖區對象完成填充并釋放,它就可以被重新使用了。是以,緩沖區一旦被讀取出來,已經沒有使用價值了。[/color]

[color=#345286] 以 Mellow 為例,填充後為 Mellow,但如果我們僅僅想讀取 llow。讀取完後,緩沖區就可以重新使用了。Me 這兩個位置對于我們而言是沒用的。我們可以将 llow 複制至 0 - 3 上,Me 則被沖掉。但是 4 和 5 仍然為 o 和 w。這個事我們當然可以自行通過 get 和 put 來完成,但 api 給我們提供了一個 compact() 的函數,此函數比我們自己使用 get 和 put 要高效的多。[/color]

[img]http://dl.iteye.com/upload/attachment/0065/1203/9a7c2133-bec2-37ca-8f6f-8cf74f35b168.png[/img]

[color=#008000] [b]Compact 之前的緩沖區[/b][/color]

[color=#345286] buffer.compact() 會使緩沖區的狀态圖如下圖所示:[/color]

[img]http://dl.iteye.com/upload/attachment/0065/1205/19cd2eeb-bddc-37d1-b2fb-a3e46e2d69fd.png[/img]

[color=#008000] [b]Compact 之後的緩沖區[/b][/color]

[color=#345286] 這裡發生了幾件事:[/color]

[list=1]

[*] [color=#345286]資料元素 2 - 5 被複制到 0 - 3 位置,位置 4 和 5 不受影響,但現在正在或已經超出了目前位置,是以是“死的”。它們可以被之後的 put() 調用重寫。[/color]

[*] [color=#345286]Position 已經被設為被複制的資料元素的數目,也就是說,緩沖區現在被定位在緩沖區中最後一個“存活”元素的後一個位置。[/color]

[*] [color=#345286]上界屬性被設定為容量的值,是以緩沖區可以被再次填滿。[/color]

[*] [color=#345286]調用 compact() 的作用是丢棄已經釋放的資料,保留未釋放的資料,并使緩沖區對重新填充容量準備就緒。該例子中,你當然可以将 Me 之前已經讀過,即已經被釋放過。[/color]

[/list]

[color=#FF0000] [b][size=medium]◇ 緩沖區的比較[/size][/b][/color]

[color=#345286] 有時候比較兩個緩沖區所包含的資料是很有必要的。所有的緩沖區都提供了一個正常的 equals() 函數用以測試兩個緩沖區的是否相等,以及一個 compareTo() 函數用以比較緩沖區。[/color]

[color=#345286] 兩個緩沖區被認為相等的充要條件是:[/color]

[list]

[*] [color=#345286]兩個對象類型相同。包含不同資料類型的 buffer 永遠不會相等,而且buffer 絕不會等于非 buffer對象。[/color]

[*] [color=#345286]兩個對象都剩餘同樣數量(limit - position)的元素。Buffer 的容量不需要相同,而且緩沖區中剩餘資料的索引也不必相同。[/color]

[*] [color=#345286]在每個緩沖區中應被 get() 函數傳回的剩餘資料元素序列([position, limit - 1] 位置對應的元素序列)必須一緻。[/color]

[/list] [img]http://dl.iteye.com/upload/attachment/0065/1225/89aa9315-882e-3632-98cd-5a16f6c1af22.png[/img]

[color=#008000] [b]兩個被認為是相等的緩沖區[/b][/color]

[img]http://dl.iteye.com/upload/attachment/0065/1227/e3e21ae4-df4b-332a-a3f1-6d88b5624c50.png[/img]

[color=#008000] [b]兩個被認為是不相等的緩沖區[/b][/color]

[color=#345286] 緩沖區也支援用 compareTo() 函數以詞典順序進行比較,當然,這是所有的緩沖區實作了 java.lang.Comparable 語義化的接口。這也意味着緩沖區數組可以通過調用 java.util.Arrays.sort() 函數按照它們的内容進行排序。[/color]

[color=#345286] 與 equals() 相似,compareTo() 不允許不同對象間進行比較。但 compareTo()更為嚴格:如果你傳遞一個類型錯誤的對象,它會抛出 ClassCastException 異常,但 equals() 隻會傳回 false。[/color]

[color=#345286] 比較是針對每個緩沖區你剩餘資料(從 position 到 limit)進行的,與它們在 equals() 中的方式相同,直到不相等的元素被發現或者到達緩沖區的上界。如果一個緩沖區在不相等元素發現前已經被耗盡,較短的緩沖區被認為是小于較長的緩沖區。這裡有個順序問題:下面小于零的結果(表達式的值為 true)的含義是 buffer2 < buffer1。切記,這代表的并不是 buffer1 < buffer2。[/color]

[color=#FF0000] [b][size=medium]◇ 批量移動[/size][/b][/color]

[color=#345286] 緩沖區的設計目的就是為了能夠高效傳輸資料,一次移動一個資料元素并不高效。如你在下面的程式清單中所看到的那樣,buffer API 提供了[/color][color=#008000][b]向緩沖區你外批量移動資料元素的函數:[/b][/color]

[color=#345286] 如你在上面的程式清單中所看到的那樣,buffer API 提供了向緩沖區内外批量移動資料元素的函數。以 get 為例,它将緩沖區中的内容複制到指定的數組中,當然是從 position 開始咯。第二種形式使用 offset 和 length 參數來指定複制到目标數組的子區間。這些批量移動的合成效果與前文所讨論的循環是相同的,但是這些方法可能高效得多,因為這種緩沖區實作能夠利用本地代碼或其他的優化來移動資料。[/color]

[color=#345286] 批量移動總是具有指定的長度。也就是說,你總是要求移動固定數量的資料元素。是以,get(dist) 和 get(dist, 0, dist.length) 是等價的。[/color]

[color=#345286] 對于以下幾種情況的資料複制會發生異常:[/color]

[list]

[*] [color=#345286]如果你所要求的數量的資料不能被傳送,那麼不會有資料被傳遞,緩沖區的狀态保持不變,同時抛出BufferUnderflowException異常。[/color]

[*] [color=#345286]如果緩沖區中的資料不夠完全填滿數組,你會得到一個異常。這意味着如果你想将一個小型緩沖區傳入一個大型數組,你需要明确地指定緩沖區中剩餘的資料長度。[/color]

[/list][color=#345286] 如果緩沖區存有比數組能容納的數量更多的資料,你可以重複利用如下代碼進行讀取:[/color]

[color=#345286] put() 的批量版本工作方式相似,隻不過它是将數組裡的元素寫入 buffer 中而已,這裡不再贅述。[/color]

[color=#FF0000] [b][size=medium]◇ 建立緩沖區[/size][/b][/color]

[color=#345286] Buffer 的七種子類,沒有一種能夠直接執行個體化,它們都是抽象類,但是都包含靜态工廠方法來建立相應類的新執行個體。這部分讨論中,将以 CharBuffer 類為例,對于其它六種主要的緩沖區類也是适用的。[/color][color=#008000][b]下面是建立一個緩沖區的關鍵函數,對所有的緩沖區類通用(要按照需要替換類名):[/b][/color]

[color=#345286] 新的緩沖區是由配置設定(allocate)或包裝(wrap)操作建立的。配置設定(allocate)操作建立一個緩沖區對象并配置設定一個私有的空間來儲存容量大小的資料元素。包裝(wrap)操作建立一個緩沖區對象但是不配置設定任何空間來儲存資料元素。它使用你所提供的數組作為存儲空間來儲存緩沖區中的資料元素。[/color][color=#008000][b]demos:[/b][/color]

[color=#345286] 通過 allocate() 或者 wrap() 函數建立的緩沖區通常都是間接的。間接的緩沖區使用備份數組,你可以通過上面列出的 api 函數獲得對這些數組的存取權。[/color]

[color=#345286] boolean 型函數 hasArray() 告訴你這個緩沖區[b]是否有一個可存取的備份數組。[/b]如果這個函數的傳回 true,array() 函數會傳回這個緩沖區對象所使用的數組存儲空間的引用。如果 hasArray() 函數傳回 false,不要調用 array() 函數或者 arrayOffset() 函數。如果你這樣做了你會得到一個 UnsupportedOperationException 異常。[/color]

[color=#345286] 如果一個緩沖區是隻讀的,它的備份數組将會是超出 limit 的,即使一個數組對象被提供給 wrap() 函數。調用 array() 函數或 arrayOffset() 會抛出一個 ReadOnlyBufferException 異常以阻止你得到存取權來修改隻讀緩沖區的内容。如果你通過其它的方式獲得了對備份數組的存取權限,對這個數組的修改也會直接影響到這個隻讀緩沖區。[/color]

[color=#345286] arrayOffset(),傳回緩沖區資料在數組中存儲的開始位置的偏移量(從數組頭 0 開始計算)。如果你使用了帶有三個參數的版本的 wrap() 函數來建立一個緩沖區,對于這個緩沖區,arrayOffset() 會一直傳回 0。不了解嗎?offset 和 length 隻是訓示了目前的 position 和 limit,是一個瞬間值,可以通過 clear() 來從 0 重新存資料,是以 arrayOffset() 傳回的是 0。當然,如果你切分(slice() 函數)了由一個數組提供存儲的緩沖區,得到的緩沖區可能會有一個非 0 的數組偏移量。[/color]

[color=#FF0000] [b][size=medium]◇ 複制緩沖區[/size][/b][/color]

[color=#345286] 緩沖區不限于管理數組中的外部資料,它們也能管理其他緩沖區中的外部資料。當一個管理其他緩沖器所包含的資料元素的緩沖器被建立時,這個緩沖器被稱為視圖緩沖器。[/color]

[color=#345286] 視圖存儲器總是通過調用已存在的存儲器執行個體中的函數來建立。使用已存在的存儲器執行個體中的工廠方法意味着視圖對象為原始存儲器的你部實作細節私有。資料元素可以直接存取,無論它們是存儲在數組中還是以一些其他的方式,而不需經過原始緩沖區對象的 get()/put() API。如果原始緩沖區是直接緩沖區,該緩沖區(視圖緩沖區)的視圖會具有同樣的效率優勢。[/color]

[color=#345286] 繼續以 CharBuffer 為例,但同樣的操作可被用于任何基本的緩沖區類型。[/color][color=#008000][b]用于複制緩沖區的 api:[/b][/color]

[color=#345286] [b]● duplidate()[/b][/color]

[color=#345286] 複制一個緩沖區會建立一個新的 Buffer 對象,但并不複制資料。原始緩沖區和副本都會操作同樣的資料元素。[/color]

[color=#345286] duplicate() 函數建立了一個與原始緩沖區相似的新緩沖區。兩個緩沖區共享資料元素,擁有同樣的容量,但每個緩沖區擁有各自的 position、limit 和 mark 屬性。對一個緩沖區你的資料元素所做的改變會反映在另外一個緩沖區上。這一副本緩沖區具有與原始緩沖區同樣的資料視圖。如果原始的緩沖區為隻讀,或者為直接緩沖區,新的緩沖區将繼承這些屬性。[/color][color=#008000][b]duplicate() 複制緩沖區:[/b][/color]

[img]http://dl.iteye.com/upload/attachment/0065/1782/709ba882-4055-3ab0-95d2-c5c8f4aacd8a.png[/img]

[color=#008000] [b]複制一個緩沖區[/b][/color]

[color=#345286] [b]● asReadOnlyBuffer()[/b][/color]

[color=#345286] asReadOnlyBuffer() 函數來生成一個隻讀的緩沖區視圖。這與duplicate() 相同,除了這個新的緩沖區不允許使用 put(),并且其 isReadOnly() 函數将會傳回 true。[/color]

[color=#345286] 如果一個隻讀的緩沖區與一個可寫的緩沖區共享資料,或者有包裝好的備份數組,那麼對這個可寫的緩沖區或直接對這個數組的改變将反映在所有關聯的緩沖區上,包括隻讀緩沖區。[/color]

[color=#345286] [b]● slice()[/b][/color]

[color=#345286] 分割緩沖區與複制相似,但 slice() 建立一個從原始緩沖區的目前 position 開始的新緩沖區,并且其容量是原始緩沖區的剩餘元素數量(limit - position)。這個新緩沖區與原始緩沖區共享一段資料元素子序列。分割出來的緩沖區也會繼承隻讀和直接屬性。[/color][color=#008000][b]slice() 分割緩沖區:[/b][/color]

[img]http://dl.iteye.com/upload/attachment/0065/1836/42d58610-006b-3079-8d0b-062b416e1b87.png[/img]

[color=#008000] [b]建立分割緩沖區[/b][/color]

[color=#FF0000] [b][size=medium]◇ 位元組緩沖區(ByteBuffer)[/size][/b][/color]

[color=#345286] ByteBuffer 隻是 Buffer 的一個子類,但位元組緩沖區有位元組的獨特之處。位元組緩沖區跟其他緩沖區類型最明顯的不同在于,它可以成為通道所執行的 I/O 的源頭或目标,後面你會發現[b]通道隻接收 ByteBuffer 作為參數。[/b][/color]

[color=#345286] 位元組是作業系統及其 I/O 裝置使用的基本資料類型。當在 JVM 和作業系統間傳遞資料時,将其他的資料類型拆分成構成它們的位元組是十分必要的,系統層次的 I/O 面向位元組的性質可以在整個緩沖區的設計以及它們互相配合的服務中感受到。同時,作業系統是在記憶體區域中進行 I/O 操作。這些記憶體區域,就作業系統方面而言,是相連的位元組序列。于是,毫無疑問,[b]隻有位元組緩沖區有資格參與 I/O 操作。[/b][/color]

[color=#345286] 非位元組類型的基本類型,除了布爾型都是由組合在一起的幾個位元組組成的。那麼必然要引出另外一個問題:位元組順序。[/color]

[color=#345286] 多位元組數值被存儲在記憶體中的方式一般被稱為 endian-ness(位元組順序)。如果數字數值的最高位元組 - big end(大端),位于低位位址(即 big end 先寫入記憶體,先寫入的記憶體的位址是低位的,後寫入記憶體的位址是高位的),那麼系統就是大端位元組順序。如果最低位元組最先儲存在記憶體中,那麼系統就是小端位元組順序。[/color][color=#008000][b]在 java.nio 中,位元組順序由 ByteOrder 類封裝:[/b][/color]

[color=#345286] ByteOrder 類定義了決定從緩沖區中存儲或檢索多位元組數值時使用哪一位元組順序的常量。如果你需要知道 JVM 運作的硬體平台的固有位元組順序,請調用靜态類函數 nativeOrder()。[/color]

[color=#008000] [b]每個緩沖區類都具有一個能夠通過調用 order() 查詢的目前位元組順序:[/b][/color]

[color=#345286] 這個函數從 ByteOrder 傳回兩個常量之一。對于除了 ByteBuffer 之外的其他緩沖區類,位元組順序是一個隻讀屬性,并且可能根據緩沖區的建立方式而采用不同的值。除了 ByteBuffer,其他通過 allocate() 或 wrap() 一個數組所建立的緩沖區将從 order() 傳回與 ByteOrder.nativeOrder() 相同的數值。這是因為包含在緩沖區中的元素在 JVM 中将會被作為基本資料直接存取。[/color]

[color=#345286] ByteBuffer 類有所不同:預設位元組順序總是 ByteBuffer.BIG_ENDIAN,無論系統的固有位元組順序是什麼。Java 的預設位元組順序是大端位元組順序,這允許類檔案等以及串行化的對象可以在任何 JVM 中工作。如果固有硬體位元組順序是小端,這會有性能隐患。在使用固有硬體位元組順序時,将 ByteBuffer 的内容當作其他資料類型存取很可能高效得多。[/color]

[color=#345286] 為什麼 ByteBuffer 類需要一個位元組順序?位元組不就是位元組嗎?ByteBuffer 對象像其他基本資料類型一樣,具有大量便利的函數用于擷取和存放緩沖區内容。這些函數對位元組進行編碼或解碼的方式取決于 ByteBuffer 目前位元組順序的設定。[/color][color=#008000][b]ByteBuffer 的位元組順序可以随時通過調用以 ByteOrder.BIG_ENDIAN 或 ByteOrder.LITTL_ENDIAN 為參數的 order() 函數來改變:[/b][/color]

[color=#345286] 如果一個緩沖區被建立為一個 ByteBuffer 對象的視圖,,那麼 order() 傳回的數值就是視圖被建立時其建立源頭的 ByteBuffer 的位元組順序。視圖的位元組順序設定在建立後不能被改變,而且如果原始的位元組緩沖區的位元組順序在之後被改變,它也不會受到影響。[/color]

[color=#FF0000] [b][size=medium]◇ 直接緩沖區[/size][/b][/color]

[color=#345286] 核心空間(與之相對的是使用者空間,如 JVM)是作業系統所在區域,它能與裝置控制器(硬體)通訊,控制着使用者區域程序(如 JVM)的運作狀态。最重要的是,所有的 I/O 都直接(實體記憶體)或間接(虛拟記憶體)通過核心空間。[/color]

[color=#345286] 當程序(如 JVM)請求 I/O 操作的時候,它執行一個系統調用将控制權移交給核心。當核心以這種方式被調用,它随即采取任何必要步驟,找到程序所需資料,并把資料傳送到使用者空間你的指定緩沖區。核心試圖對資料進行高速緩存或預讀取,是以程序所需資料可能已經在核心空間裡了。如果是這樣,該資料隻需簡單地拷貝出來即可。如果資料不在核心空間,則程序被挂起,核心着手把資料讀進記憶體。[/color]

[img]http://dl.iteye.com/upload/attachment/0065/3248/b6987b55-55f9-3e85-9f49-4c7642c53aa6.png[/img]

[color=#008000] [b]I/O 緩沖區操作簡圖[/b][/color]

[color=#345286] 從圖中你可能會覺得,把資料從核心空間拷貝到使用者空間似乎有些多餘。為什麼不直接讓磁盤控制器把資料送到使用者空間的緩沖區呢?首先,硬體通常不能直接通路使用者空間。其次,像磁盤這樣基于塊存儲的硬體裝置操作的是固定大小的資料塊,而使用者程序請求的可能是任意大小的或非對齊的資料塊。在資料往來于使用者空間與儲存設備的過程中,核心負責資料的分解、再組合工作,是以充當着中間人的角色。[/color]

[color=#345286] 是以,作業系統是在記憶體區域中進行 I/O 操作。這些記憶體區域,就作業系統方面而言,是相連的位元組序列,這也意味着I/O操作的目标記憶體區域必須是連續的位元組序列。在 JVM中,位元組數組可能不會在記憶體中連續存儲(因為 JAVA 有 GC 機制),或者無用存儲單元(會被垃圾回收)收集可能随時對其進行移動。[/color]

[color=#345286] 出于這個原因,引入了直接緩沖區的概念。直接位元組緩沖區通常是 I/O 操作最好的選擇。非直接位元組緩沖區(即通過 allocate() 或 wrap() 建立的緩沖區)可以被傳遞給通道,但是這樣可能導緻性能損耗。通常非直接緩沖不可能成為一個本地 I/O 操作的目标。[/color]

[color=#345286] 如果你向一個通道中傳遞一個非直接 ByteBuffer 對象用于寫入,通道可能會在每次調用中隐含地進行下面的操作:[/color]

[list=1]

[*] [color=#345286]建立一個臨時的直接 ByteBuffer 對象。[/color]

[*] [color=#345286]将非直接緩沖區的内容複制到臨時直接緩沖區中。[/color]

[*] [color=#345286]使用臨時直接緩沖區執行低層 I/O 操作。[/color]

[*] [color=#345286]臨時直接緩沖區對象離開作用域,并最終成為被回收的無用資料。[/color]

[/list][color=#345286] 這可能導緻緩沖區在每個 I/O 上複制并産生大量對象,而這種事都是我們極力避免的。如果你僅僅為一次使用而建立了一個緩沖區,差別并不是很明顯。另一方面,如果你将在一段高性能腳本中重複使用緩沖區,配置設定直接緩沖區并重新使用它們會使你遊刃有餘。[/color]

[color=#345286] 直接緩沖區可能比建立非直接緩沖區要花費更高的成本,它使用的記憶體是通過調用本地作業系統方面的代碼配置設定的,繞過了标準 JVM 堆棧,不受垃圾回收支配,因為它們位于标準 JVM 堆棧之外。[/color]

[color=#345286] 直接 ByteBuffer 是通過調用具有所需容量的 ByteBuffer.allocateDirect() 函數産生的。注意,wrap() 函數所建立的被包裝的緩沖區總是非直接的。[/color][color=#008000][b]與直接緩沖區相關的 api:[/b][/color]

[color=#345286] 所有的緩沖區都提供了一個叫做 isDirect() 的 boolean 函數,來測試特定緩沖區是否為直接緩沖區。但是,[b]ByteBuffer 是唯一可以被配置設定成直接緩沖區的 Buffer。[/b]盡管如此,如果基礎緩沖區是一個直接 ByteBuffer,對于非位元組視圖緩沖區,isDirect() 可以是 true。[/color]

[color=#FF0000] [b][size=medium]◇ 視圖緩沖區[/size][/b][/color]

[color=#345286] I/O 基本上可以歸結成組位元組資料的四處傳遞,在進行大資料量的 I/O 操作時,很又可能你會使用各種 ByteBuffer 類去讀取檔案内容,接收來自網絡連接配接的資料,等等。ByteBuffer 類提供了豐富的 API 來建立視圖緩沖區。[/color]

[color=#345286] 視圖緩沖區通過已存在的緩沖區對象執行個體的工廠方法來建立。這種視圖對象維護它自己的屬性,容量,位置,上界和标記,但是和原來的緩沖區共享資料元素。[/color]

[color=#345286] 每一個工廠方法都在原有的 ByteBuffer 對象上建立一個視圖緩沖區。調用其中的任何一個方法都會建立對應的緩沖區類型,這個緩沖區是基礎緩沖區的一個切分,由基礎緩沖區的位置和上界決定。新的緩沖區的容量是位元組緩沖區中存在的元素數量除以視圖類型中組成一個資料類型的位元組數,在切分中任一個超過上界的元素對于這個視圖緩沖區都是不可見的。視圖緩沖區的第一個元素從建立它的 ByteBuffer 對象的位置開始(positon() 函數的傳回值)。[/color][color=#008000][b]來自 ByteBuffer 建立視圖緩沖區的工廠方法:[/b][/color]

[color=#345286] 下面的代碼建立了一個 ByteBuffer 緩沖區的 CharBuffer 視圖。[/color][color=#008000][b]示範 7 個位元組的 ByteBuffer 的 CharBuffer 視圖:[/b][/color]

[img]http://dl.iteye.com/upload/attachment/0065/3311/1386c0c4-b53e-3e5b-8bd0-c4a9a4a1f7dd.png[/img]

[color=#008000] [b]7 個 位元組的 ByteBuffer 的 CharBuffer 視圖[/b][/color]

[color=#FF0000] [b][size=medium]◇ 資料元素視圖[/size][/b][/color]

[color=#008000] [b]ByteBuffer 類為每一種原始資料類型提供了存取的和轉化的方法:[/b][/color]

[color=#345286] 這些函數從目前位置開始存取 ByteBuffer 的位元組資料,就好像一個資料元素被存儲在那裡一樣。根據這個緩沖區的目前的有效的位元組順序,這些位元組資料會被排列或打亂成需要的原始資料類型。[/color]

[color=#345286] 如果 getInt() 函數被調用,從目前的位置開始的四個位元組會被包裝成一個 int 類型的變量然後作為函數的傳回值傳回。實際的傳回值取決于緩沖區的目前的比特排序(byte-order)設定。[/color][color=#008000][b]不同位元組順序取得的值是不同的:[/b][/color]

[color=#345286] 如果你試圖擷取的原始類型需要比緩沖區中存在的位元組數更多的位元組,會抛出 BufferUnderflowException。[/color]

繼續閱讀