Java ByteBuffer –速成课程
以我的經(jīng)驗(yàn),當(dāng)開發(fā)人員第一次遇到j(luò)ava.nio.ByteBuffer時,會引起混亂和細(xì)微的錯誤,因?yàn)槿绾握_使用它尚不明顯。 在我對API文檔感到滿意之前,需要反復(fù)閱讀API文檔和一些經(jīng)驗(yàn)以實(shí)現(xiàn)一些微妙之處。 這篇文章是關(guān)于如何正確使用它們的短暫崩潰,希望可以為其他人節(jié)省一些麻煩。
由于所有這些都是基于推斷(而不是基于明確的文檔),并且是基于經(jīng)驗(yàn),因此我不能斷言這些信息必定是權(quán)威的。 我歡迎您提出反饋意見,以指出錯誤或其他觀點(diǎn)。 我也歡迎提出其他陷阱/最佳做法的建議。
我確實(shí)假定讀者將閱讀與本文相關(guān)的API文檔。 我不會窮盡所有您可以使用ByteBuffer進(jìn)行的操作。
ByteBuffer抽象
可以將ByteBuffer看作提供了一些(未定義的)底層字節(jié)存儲的視圖 。 字節(jié)緩沖區(qū)的兩種最常見的具體類型是由字節(jié)數(shù)組支持的字節(jié)緩沖區(qū)和由直接(脫離堆,本機(jī))字節(jié)緩沖區(qū)支持的字節(jié)緩沖區(qū)。 在兩種情況下,都可以使用相同的接口讀取和寫入緩沖區(qū)的內(nèi)容。
ByteBuffer的API的某些部分特定于某些類型的字節(jié)緩沖區(qū)。 例如,字節(jié)緩沖區(qū)可以是只讀的 ,將用法限制為方法的子集。 array()方法僅適用于由字節(jié)數(shù)組支持的字節(jié)緩沖區(qū)(可以使用hasArray()進(jìn)行測試),并且通常僅在完全知道自己在做什么的情況下使用 。 一個常見的錯誤是使用array()將ByteBuffer“轉(zhuǎn)換”為字節(jié)數(shù)組。 這不僅僅適用于字節(jié)數(shù)組支持的緩沖區(qū),而且很容易成為錯誤的來源,因?yàn)楦鶕?jù)緩沖區(qū)的創(chuàng)建方式,返回數(shù)組的開頭可能與字節(jié)緩沖區(qū)的開頭相對應(yīng), 也可能不對應(yīng)。 結(jié)果往往是一個細(xì)微的錯誤,其中代碼的行為根據(jù)字節(jié)緩沖區(qū)和創(chuàng)建它的代碼的實(shí)現(xiàn)細(xì)節(jié)而有所不同。
ByteBuffer可以通過調(diào)用repeat()復(fù)制自身。 這實(shí)際上并不復(fù)制基礎(chǔ)字節(jié) ,而只是創(chuàng)建一個指向相同基礎(chǔ)存儲的新ByteBuffer實(shí)例。 可以使用slice()創(chuàng)建表示另一個ByteBuffer的子集的ByteBuffer。
與字節(jié)數(shù)組的主要區(qū)別
- ByteBuffer具有關(guān)于hashCode() / equals()的值語義,因此可以更方便地在容器中使用。
- ByteBuffer通過實(shí)例化新的ByteBuffer,提供了將字節(jié)緩沖區(qū)的子集作為值傳遞而不復(fù)制字節(jié)的功能。
- NIO API大量使用了ByteBuffer:s。
- ByteBuffer中的字節(jié)可能駐留在Java堆之外。
- ByteBuffer的狀態(tài)超出了字節(jié)本身,這有利于進(jìn)行相對的I / O操作(但有一些警告,請參見下文)。
- ByteBuffer提供了用于讀取和寫入各種原始類型(如整數(shù)和long)的方法(并且可以按不同的字節(jié)順序進(jìn)行操作)。
ByteBuffer的關(guān)鍵屬性
ByteBuffer的以下三個屬性至關(guān)重要(我在每個屬性上引用了API文檔):
- 緩沖區(qū)的容量是它包含的元素數(shù)量。 緩沖區(qū)的容量永遠(yuǎn)不會為負(fù),也不會改變。
- 緩沖區(qū)的限制是不應(yīng)讀取或?qū)懭氲牡谝粋€元素的索引。 緩沖區(qū)的限制永遠(yuǎn)不會為負(fù),也永遠(yuǎn)不會大于緩沖區(qū)的容量。
- 緩沖區(qū)的位置是下一個要讀取或?qū)懭氲脑氐乃饕?緩沖區(qū)的位置永遠(yuǎn)不會為負(fù),也不會大于其限制。
這是一個示例ByteBuffer的可視化示例,在示例中,ByteBuffer由字節(jié)數(shù)組支持,并且ByteBuffer的值是單詞“ test”(單擊以放大):
該ByteBuffer等于(在equals()的意義上) 等于其在[ position , limit )之間內(nèi)容相同的任何其他ByteBuffer。
假設(shè)上面顯示的字節(jié)緩沖區(qū)是bb ,我們這樣做:
final ByteBuffer other = bb.duplicate(); other.position(bb.position() + 4);現(xiàn)在,我們將有兩個ByteBuffer實(shí)例都引用相同的基礎(chǔ)字節(jié)數(shù)組,但是它們的內(nèi)容將有所不同( 其他將為空):
字節(jié)緩沖區(qū)的緩沖區(qū)/流對偶
有兩種訪問字節(jié)緩沖區(qū)內(nèi)容的方法- 絕對訪問和相對訪問。 例如,假設(shè)我有一個ByteBuffer,我知道它包含兩個整數(shù)。 為了使用絕對定位提取整數(shù),可以這樣做:
int first = bb.getInt(0) int second = bb.getInt(4)或者,可以使用相對定位提取它們:
int first = bb.getInt(); int second = bb.getInt();第二種選擇通常很方便,但是以對緩沖區(qū)產(chǎn)生副作用 (即更改它)為代價。 不是內(nèi)容本身,而是ByteBuffers視圖可以查看該內(nèi)容。
這樣,如果將ByteBuffer用作流,則其行為類似于流。
最佳做法和陷阱
flip()緩沖區(qū)
如果要通過重復(fù)寫入來構(gòu)建ByteBuffer,然后想將其贈送,則必須記住將它翻轉(zhuǎn)() 。 例如,這是一種將字節(jié)數(shù)組復(fù)制到ByteBuffer的方法,并假設(shè)使用默認(rèn)編碼(請注意,此處使用的ByteBuffer.wrap()創(chuàng)建一個包裝指定字節(jié)數(shù)組的ByteBuffer,而不是復(fù)制其中的內(nèi)容放入新的ByteBuffer中):
public static ByteBuffer fromByteArray(byte[] bytes) {final ByteBuffer ret = ByteBuffer.wrap(new byte[bytes.length]);ret.put(bytes);ret.flip();return ret; }如果我們不翻轉(zhuǎn)它,則返回的ByteBuffer 將為空,因?yàn)樵撐恢玫扔趌imit 。
不要消耗緩沖區(qū)
除非特別打算這樣做,否則在讀取字節(jié)緩沖區(qū)時請注意不要“消耗”它。 例如,考慮采用默認(rèn)編碼方式,將ByteBuffer轉(zhuǎn)換為String的此方法:
public static String toString(ByteBuffer bb) {final byte[] bytes = new byte[bb.remaining()];bb.duplicate().get(bytes);return new String(bytes); }不幸的是,沒有提供進(jìn)行字節(jié)數(shù)組的絕對定位讀取的方法(但確實(shí)存在用于基元的絕對定位讀取)。
注意在讀取字節(jié)時使用了plicate() 。 如果我們不這樣做,該函數(shù)將對輸入ByteBuffer產(chǎn)生副作用 。 這樣做的代價是僅為了一次調(diào)用get()的目的就額外分配了一個新的ByteBuffer。 您可以在get()之前記錄ByteBuffer的位置,然后再將其還原,但這存在線程安全性問題(請參閱下一節(jié))。
值得注意的是,這僅在您嘗試將ByteBuffer:s視為值時適用。 如果您正在編寫旨在對ByteBuffer產(chǎn)生副作用的代碼,將它們更像流一樣對待,那么您當(dāng)然打算這樣做,并且本節(jié)不適用。
不要改變緩沖區(qū)
在不是特定于特定用例的通用代碼的情況下,(在我看來)對于執(zhí)行(抽象)只讀操作(例如讀取字節(jié)緩沖區(qū))的方法來說是一種好習(xí)慣。 ,不更改其輸入。 這是比“不要消耗ByteByffer”更強(qiáng)的要求。 以上一節(jié)中的示例為例,但嘗試避免額外分配ByteBuffer:
public static String toString(ByteBuffer bb) {final byte[] bytes = new byte[bb.remaining()];bb.mark(); // NOT RECOMMENDED, don't do thisbb.get(bytes);bb.reset(); // NOT RECOMMENDED, don't do thisreturn new String(bytes); }在這種情況下,我們在調(diào)用get()之前記錄ByteBuffer的狀態(tài),然后再進(jìn)行恢復(fù)(請參閱API文檔中的mark()和reset() )。 這種方法有兩個問題。 第一個問題是上面的函數(shù)沒有組成 。 一個ByteBuffer僅具有一個“標(biāo)記”,并且您的(非常通用,不具有上下文意識) toString()方法不能安全地假定調(diào)用者并未出于自身目的嘗試使用mark()和reset() 。 例如,假設(shè)以下調(diào)用者正在反序列化一個長度為前綴的字符串:
bb.mark(); int length = bb.getInt(); ... sanity check length final String str = ByteBufferUtils.toString(bb); ... do something bb.reset(); // OOPS - reset() will now point 4 bytes off, because toString() modified the mark(順便說一句,這是一個非常人為且奇怪的示例,因?yàn)槲野l(fā)現(xiàn)很難提出一個使用mark() / reset()的實(shí)際代碼示例,該代碼通常在處理流中的緩沖區(qū)時使用,像派系一樣,也感覺需要在所述緩沖區(qū)的其余部分上調(diào)用toString() 。我很想聽聽人們在這里提出了什么解決方案。例如,可以想象一個清晰的代碼庫中的策略在類似于toString()的面向值的上下文中允許mark() / reset() –但是即使您這樣做了(它可能會無意中違反了它的味道),您仍然會遭受后面提到的突變問題。)
讓我們看一下避免這種問題的toString()的替代版本:
public static String toString(ByteBuffer bb) {final byte[] bytes = new byte[bb.remaining()];bb.get(bytes);bb.position(bb.position() - bytes.length); // NOT RECOMMENDED, don't do thisreturn new String(bytes); }在這種情況下,我們不修改標(biāo)記,因此我們進(jìn)行撰寫。 但是,我們?nèi)匀恢铝τ诟淖冚斎氲摹白镄小薄?在多線程情況下,這是一個問題。 除非抽象隱含了該內(nèi)容(例如,使用流或以類似流的方式使用ByteBuffer時),否則您不希望閱讀暗示其變化的內(nèi)容。 如果要傳遞的ByteBuffer視為一個值,將其放入容器中,共享它們,等等–除非保證兩個線程永遠(yuǎn)不會同時使用同一個ByteBuffer,否則對它們進(jìn)行突變將引入細(xì)微的錯誤。 通常,此類錯誤的結(jié)果是奇怪的值損壞或意外的BufferOverFlowException:s。
不受此影響的版本出現(xiàn)在上面的“不要使用緩沖區(qū)”部分,該部分使用duplicate()構(gòu)造一個臨時的ByteBuffer實(shí)例,可以在其上安全調(diào)用get() 。
compareTo()受字節(jié)簽名的約束
Java中的字節(jié)是有符號的 ,這與通常期望的相反。 但是,容易錯過的是,這也會影響B(tài)yteBuffer.compareTo() 。 該方法的Java API文檔顯示為:
“通過按字典順序比較剩余字節(jié)的序列來比較兩個字節(jié)緩沖區(qū),而不考慮每個序列在其相應(yīng)緩沖區(qū)中的開始位置?!?
快速閱讀可能會使人相信結(jié)果通常是您期望的,但是當(dāng)然,鑒于Java中字節(jié)的定義,情況并非如此。 結(jié)果是,包含最高位設(shè)置的值的字節(jié)緩沖區(qū)的順序?qū)⑴c您期望的有所不同。
Google出色的Guava庫具有UnsignedBytes幫助器 ,可減輕您的痛苦。
array()通常是使用錯誤的方法
通常,不要隨便使用array() 。 為了正確使用它,您要么必須知道字節(jié)緩沖區(qū)是數(shù)組支持的事實(shí) ,要么必須使用 hasArray() 對其進(jìn)行測試,并且在兩種情況下都有兩個單獨(dú)的代碼路徑。 此外,在使用它時, 必須使用arrayOffset()以確定ByteBuffer的第零個位置與字節(jié)數(shù)組相對應(yīng)。
在典型的應(yīng)用程序代碼中,除非您真的知道自己在做什么并且特別需要它,否則您將不會使用array() 。 也就是說,在某些情況下它很有用。 例如,假設(shè)您實(shí)現(xiàn)的是UnsignedBytes.compare()的ByteBuffer版本(同樣來自Guava )–您可能希望優(yōu)化其中一個或兩個參數(shù)都支持?jǐn)?shù)組的情況,以避免不必要的復(fù)制和頻繁調(diào)用。緩沖區(qū)。 對于這種通用且可能大量使用的方法,這種優(yōu)化是有意義的。
參考: Java ByteBuffer –我們的JCG合作伙伴 Peter Schuller在(mod:world:scode)博客上的速成班 。
翻譯自: https://www.javacodegeeks.com/2012/12/the-java-bytebuffer-a-crash-course.html
總結(jié)
以上是生活随笔為你收集整理的Java ByteBuffer –速成课程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米路由器如何绑定小米账号qq如何绑定小
- 下一篇: Java EE 7社区调查结果!