学习笔记之ByteBuffer使用和实现以及文件内存映射
轉載自??學習筆記之ByteBuffer使用和實現以及文件內存映射
ByteBuffer和CharBuffer等其它Buffer的直接子類一樣,顧名思義,就是存取字節的Buffer。很多數據最終在和底層交互上都是使用了字節,而更大的數據是由字節組合而成。談到字節的組合,就不得不談到字節大小的定義和字節的順序。關于字節是8位構成的這個結論,似乎現在的計算機教材都理所當然地描述出來,我們也默認接受了這樣的一個事實。但實際上字節由8個二進制位構成也是有淵源和優點的,這與IBM的360主機有關,詳細的可以參考這個。下面說說組成數據的字節順序。
對于多字節的數據在系統中的存儲,通常按數據的高位和低位在系統內存中的高地址和低地址存放分為大端(big endian)和小端(little endian)兩種方式。
在Java API中,有ByteOrder這樣一個public類,在其中定義了大端和小端兩個常量。通過這個java.nio.ByteOrder類的nativeOrder()方法,也可以確定當前系統平臺的字節順序。
在不同的平臺上可能有不同的字節順序標準。但在ByteBuffer類中,默認是使用了ByteOrder.BIG_ENDIAN字節序。但可以通過ByteBuffer的重載方法獲取和設置字節序:
- public final ByteOrder order( )
- public final ByteBuffer order (ByteOrder bo)
1. ByteBuffer的實現
提到ByteBuffer的實現,我們先來看下Win下JDK實現的類層次結構圖。
Win下JDK的ByteBuffer類層次結構圖
而在Oracle的Java SE API中,實際上只提到了MappedByteBuffer。所以堆實現和具體的直接實現(DirectByteBuffer)我們只簡單了解就行了,因為這個不在API中,和平臺實現相關。
HeapByteBuffer 是虛擬機的堆中實現,DirectByteBuffer是系統級別實現(使用unsafe的 unsafe.allocateMemory(size)),使用時后者比前者節省了拷貝過程,但后者的構建和析構成本更高,總體性能需要具體問題綜合分析。而且DirectByteBuffer會受到平臺方面的約束,使用時需要小心注意。
而API中出現了的java.nio.MappedByteBuffer則是針對文件映射工作的,也是一種Direct的ByteBuffer。除了繼承ByteBuffer類的方法外,API還提供了下面3個方法:
- public final MappedByteBuffer force()
- public final boolean isLoaded()
- public final MappedByteBuffer load()
關于文件映射相關的具體內容,下面會詳細說。
2. ByteBuffer和Buffer的其它直接子類之間的關系
前面的一篇Buffer的文章提到了它的幾個直接子類,分別是ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer。除了ByteBuffer,還有另外6種,而這些也都和Java的基本數據類型有一定的對應關系,下面我們對使用上的情況梳理下。
ByteBuffer繼承于Buffer。和CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer一樣,都是抽象類。
在Java NIO中,除了Buffer還有Channel等類。Buffer也只有和Channel配合才能充分地把Java NIO使用起來。而Channel中的很多方法都是使用ByteBuffer作為參數和返回結果進行傳遞的,好處就是Byte是字節、是基礎,而方法也簡單了很多。
而這樣的設計有一個要求,就是使用Byte以外的類型也能很好的利用到ByteBuffer,這其中有兩種方式:
- 一種是View Buffer,即直接通過ByteBuffer的數據結構做支持,得到另外一種類型Buffer對象
- 另一種就是Data Element View,即不通過CharBuffer等類對象和ByteBuffer的互相轉換獲取,而是直接使用ByteBuffer自帶的基本類型put和get方法
下面是兩段代碼例子。
當然,在實際使用過程中也會有需要注意的問題,比如字符數據,就需要考慮字符集編碼的問題。下面是《Thinking in Java》中的例子:
3. 內存映射和ByteBuffer的使用
這段內容將簡單說明下文件內存映射的概念和ByteBuffer的其它點。
ByteBuffer的最基本使用,和上一篇NIO中講CharBuffer等Buffer的直接子類一樣,就是put()和get()。為提高效率,除了單個字節讀寫,有整塊的操作方法,即對get()和put()的重載方法。
而內存映射這個概念最初一直困惑了我很久才搞明白,但實際上原理并不復雜,只需要了解操作系統工作的最基本原理。我們通常的直接通過API做IO,會用到一系列的系統調用(system call),之后通過驅動程序和外部設備交互來完成輸入輸出操作,磁盤上的文件讀寫也是一樣。而文件內存映射之所以得到很好的使用是因為,使用了文件的內存映射可以大大提高效率。提高效率的點就在于,不必每個IO操作都經過系統調用來完成,這個效率是相對較低的,而是巧妙靈活地使用內存管理系統。我們都知道當程序需要使用大量內存而實際物理內存較小的時候,我們的內存管理系統會進行頁的換入換出操作,使部分當前使用不到的內存頁放到磁盤上去,而缺頁中斷又會相應的做換入操作 —— 這就是內存映射的基礎。
在Java中,在Java NIO的FileChannel類中,提供了一個map()方法,這個方法返回的結果就是一個MappedByteBuffer類的對象,也就是一個ByteBuffer對象。這使得我們對磁盤上文件內容的讀寫,完全可以像對其他Buffer一樣,進行put()和get()。
4. 其它一些實現細節
這是一些未深入整理的實現細節點,在Win下的Oracle/Sun JDK:
- ByteBuffer的具體實現,也是基于byte數組和對應的offset
- 有array()和arrayOffset()抽象未實現方法
- 還有address屬性,DirectBuffer才會用到
- 還有只讀等屬性和其它方法等
總結
以上是生活随笔為你收集整理的学习笔记之ByteBuffer使用和实现以及文件内存映射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于ByteBuffer使用解释
- 下一篇: 百度文心大模型 4.0 发布,李彦宏称综