小師妹學JavaIO之:MappedByteBuffer多大的檔案我都裝得下
阿新 • • 發佈:2020-06-12
[toc]
# 簡介
大大大,我要大!小師妹要讀取的檔案越來越大,該怎麼幫幫她,讓程式在效能和速度上面得到平衡呢?快來跟F師兄一起看看吧。
# 虛擬地址空間
小師妹:F師兄,你有沒有發現,最近硬碟的價格真的是好便宜好便宜,1T的硬碟大概要500塊,平均1M五毛錢。現在下個電影都1G起步,這是不是意味著我們買入了大資料時代?
沒錯,小師妹,硬體技術的進步也帶來了軟體技術的進步,兩者相輔相成,缺一不可。
小師妹:F師兄,如果要是去讀取G級的檔案,有沒有什麼快捷簡單的方法?
更多精彩內容且看:
* [區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新](http://www.flydean.com/blockchain/)
* [Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新](http://www.flydean.com/learn-spring-boot/)
* [Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新](http://www.flydean.com/spring5/)
* [java程式設計師從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程](https://blog.csdn.net/superfjj/article/details/105482751)
還記得上次我們講的虛擬地址空間嗎?
再把上次講的圖搬過來:
![](https://img-blog.csdnimg.cn/20200513225239404.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
通常來說我們的應用程式呼叫系統的介面從磁碟空間獲取Buffer資料,我們把自己的應用程式稱之為使用者空間,把系統的底層稱之為系統空間。
傳統的IO操作,是作業系統講磁碟中的檔案讀入到系統空間裡面,然後再拷貝到使用者空間中,供使用者使用。
這中間多了一個Buffer拷貝的過程,如果這個量夠大的話,其實還是挺浪費時間的。
於是有人在想了,拷貝太麻煩太耗時了,我們單獨劃出一塊記憶體區域,讓系統空間和使用者空間同時對映到同一塊地址不就省略了拷貝的步驟嗎?
這個被劃出來的單獨的記憶體區域叫做虛擬地址空間,而不同空間到虛擬地址的對映就叫做Buffer Map。 Java中是有一個專門的MappedByteBuffer來代表這種操作。
小師妹:F師兄,那這個虛擬地址空間和記憶體有什麼區別呢?有了記憶體還要啥虛擬地址空間?
虛擬地址空間有兩個好處。
第一個好處就是虛擬地址空間對於應用程式本身而言是獨立的,從而保證了程式的互相隔離和程式中地址的確定性。比如說一個程式如果執行在虛擬地址空間中,那麼它的空間地址是固定的,不管他執行多少次。如果直接使用記憶體地址,那麼可能這次執行的時候記憶體地址可用,下次執行的時候記憶體地址不可用,就會導致潛在的程式出錯。
第二個好處就是虛擬空間地址可以比真實的記憶體地址大,這個大其實是對記憶體的使用做了優化,比如說會把很少使用的記憶體寫如磁碟,從而釋放出更多的記憶體來做更有意義的事情,而之前儲存到磁碟的資料,當真正需要的時候,再從磁碟中載入到記憶體中。
這樣實體記憶體實際上可以看做虛擬空間地址的快取。
# 詳解MappedByteBuffer
小師妹:MappedByteBuffer聽起來好神奇,怎麼使用它呢?
我們先來看看MappedByteBuffer的定義:
~~~java
public abstract class MappedByteBuffer
extends ByteBuffer
~~~
它實際上是一個抽象類,具體的實現有兩個:
~~~java
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer
~~~
~~~java
class DirectByteBufferR extends DirectByteBuffer
implements DirectBuffer
~~~
分別是DirectByteBuffer和DirectByteBufferR。
小師妹:F師兄,這兩個ByteBuffer有什麼區別呢?這個R是什麼意思?
R代表的是ReadOnly的意思,可能是因為本身是個類的名字就夠長了,所以搞了個縮寫。但是也不寫個註解,讓人看起來十分費解....
我們可以從RandomAccessFile的FilChannel中呼叫map方法獲得它的例項。
我們看下map方法的定義:
~~~java
public abstract MappedByteBuffer map(MapMode mode, long position, long size)
throws IOException;
~~~
MapMode代表的是對映的模式,position表示是map開始的地址,size表示是ByteBuffer的大小。
## MapMode
小師妹:F師兄,檔案有隻讀,讀寫兩種模式,是不是MapMode也包含這兩類?
對的,其實NIO中的MapMode除了這兩個之外,還有一些其他很有趣的用法。
* FileChannel.MapMode.READ_ONLY 表示只讀模式
* FileChannel.MapMode.READ_WRITE 表示讀寫模式
* FileChannel.MapMode.PRIVATE 表示copy-on-write模式,這個模式和READ_ONLY有點相似,它的操作是先對原資料進行拷貝,然後可以在拷貝之後的Buffer中進行讀寫。但是這個寫入並不會影響原資料。可以看做是資料的本地拷貝,所以叫做Private。
基本的MapMode就這三種了,其實除了基礎的MapMode,還有兩種擴充套件的MapMode:
* ExtendedMapMode.READ_ONLY_SYNC 同步的讀
* ExtendedMapMode.READ_WRITE_SYNC 同步的讀寫
# MappedByteBuffer的最大值
小師妹:F師兄,既然可以對映到虛擬記憶體空間,那麼這個MappedByteBuffer是不是可以無限大?
當然不是了,首先虛擬地址空間的大小是有限制的,如果是32位的CPU,那麼一個指標佔用的地址就是4個位元組,那麼能夠表示的最大值是0xFFFFFFFF,也就是4G。
另外我們看下map方法中size的型別是long,在java中long能夠表示的最大值是0x7fffffff,也就是2147483647位元組,換算一下大概是2G。也就是說MappedByteBuffer的最大值是2G,一次最多隻能map 2G的資料。
# MappedByteBuffer的使用
小師妹,F師兄我們來舉兩個使用MappedByteBuffer讀寫的例子吧。
善!
先看一下怎麼使用MappedByteBuffer來讀資料:
~~~java
public void readWithMap() throws IOException {
try (RandomAccessFile file = new RandomAccessFile(new File("src/main/resources/big.www.flydean.com"), "r"))
{
//get Channel
FileChannel fileChannel = file.getChannel();
//get mappedByteBuffer from fileChannel
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
// check buffer
log.info("is Loaded in physical memory: {}",buffer.isLoaded()); //只是一個提醒而不是guarantee
log.info("capacity {}",buffer.capacity());
//read the buffer
for (int i = 0; i < buffer.limit(); i++)
{
log.info("get {}", buffer.get());
}
}
}
~~~
然後再看一個使用MappedByteBuffer來寫資料的例子:
~~~java
public void writeWithMap() throws IOException {
try (RandomAccessFile file = new RandomAccessFile(new File("src/main/resources/big.www.flydean.com"), "rw"))
{
//get Channel
FileChannel fileChannel = file.getChannel();
//get mappedByteBuffer from fileChannel
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096 * 8 );
// check buffer
log.info("is Loaded in physical memory: {}",buffer.isLoaded()); //只是一個提醒而不是guarantee
log.info("capacity {}",buffer.capacity());
//write the content
buffer.put("www.flydean.com".getBytes());
}
}
~~~
# MappedByteBuffer要注意的事項
小師妹:F師兄,MappedByteBuffer因為使用了記憶體對映,所以讀寫的速度都會有所提升。那麼我們在使用中應該注意哪些問題呢?
MappedByteBuffer是沒有close方法的,即使它的FileChannel被close了,MappedByteBuffer仍然處於開啟狀態,只有JVM進行垃圾回收的時候才會被關閉。而這個時間是不確定的。
# 總結
本文再次介紹了虛擬地址空間和MappedByteBuffer的使用。
本文的例子[https://github.com/ddean2009/learn-java-io-nio](https://github.com/ddean2009/learn-java-io-nio)
> 本文作者:flydean程式那些事
>
> 本文連結:[http://www.flydean.com/io-nio-mappedbytebuffer/ ](http://www.flydean.com/io-nio-mappedbytebuffer/ )
>
> 本文來源:flydean的部落格
>
> 歡迎關注我的公眾號:程式那些事,更多精彩等著您!