之前跟大家說過,要講MappedByteBuffer,現在我來履行承諾了。
首先從大體上講一下MappedByteBuffer究竟是什么。從繼承結構上來講,MappedByteBuffer繼承自ByteBuffer,所以ByteBuffer有的能力它全有;像變動position和limit指針啦、包裝一個其他種類Buffer的視圖啦,都可以。“MappedByteBuffer”為何而來?吾輩心中亦有惑(熊貓人之謎的梗)用一個字來概括就是快
為什么快?因為它使用direct buffer的方式讀寫文件內容,這種方式的學名叫做內存映射。這種方式直接調用系統底層的緩存,沒有JVM和系統之間的復制操作,所以效率大大的提高了。而且由於它這么快,還可以用它來在進程(或線程)間傳遞消息,基本上能達到和“共享內存頁”相同的作用,只不過它是依托實體文件來運行的。
而且它還有另一種能力。就是它可以讓我們讀寫那些因為太大而不能放進內存中的文件。有了它,我們就可以假定整個文件都放在內存中(實際上,大文件放在內存和虛擬內存中),基本上都可以將它當作一個特別大的數組來訪問,這樣極大的簡化了對於大文件的修改等操作。
下面我們開始介紹它的用法了
FileChannel提供了map方法來把文件映射為MappedByteBuffer: MappedByteBuffer map(int mode,long position,long size); 可以把文件的從position開始的size大小的區域映射為MappedByteBuffer,mode指出了可訪問該內存映像文件的方式,共有三種,分別為:
MapMode.READ_ONLY
(只讀): 試圖修改得到的緩沖區將導致拋出 ReadOnlyBufferException。
MapMode.READ_WRITE
(讀/寫): 對得到的緩沖區的更改最終將寫入文件;但該更改對映射到同一文件的其他程序不一定是可見的(無處不在的“一致性問題”又出現了)。
MapMode.PRIVATE
(專用): 可讀可寫,但是修改的內容不會寫入文件,只是buffer自身的改變,這種能力稱之為”copy on write”
再簡單的說一下,MappedByteBuffer較之ByteBuffer新增的三個方法
- fore()緩沖區是READ_WRITE模式下,此方法對緩沖區內容的修改強行寫入文件
- load()將緩沖區的內容載入內存,並返回該緩沖區的引用
- isLoaded()如果緩沖區的內容在物理內存中,則返回真,否則返回假
下面代碼終於出場了
int length = 0x8FFFFFF;//一個byte占1B,所以共向文件中存128M的數據
try (FileChannel channel = FileChannel.open(Paths.get("src/c.txt"),
StandardOpenOption.READ, StandardOpenOption.WRITE);) {
MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
for(int i=0;i<length;i++) {
mapBuffer.put((byte)0);
}
for(int i = length/2;i<length/2+4;i++) {
//像數組一樣訪問
System.out.println(mapBuffer.get(i));
}
}
上面是MappedByteBuffer最基本的應用,而下面這段代碼主要是測試它到底有多快,
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class TestMappedByteBuffer {
private static int length = 0x2FFFFFFF;//1G
private abstract static class Tester {
private String name;
public Tester(String name) {
this.name = name;
}
public void runTest() {
System.out.print(name + ": ");
long start = System.currentTimeMillis();
test();
System.out.println(System.currentTimeMillis()-start+" ms");
}
public abstract void test();
}
private static Tester[] testers = {
new Tester("Stream RW") {
public void test() {
try (FileInputStream fis = new FileInputStream(
"src/a.txt");
DataInputStream dis = new DataInputStream(fis);
FileOutputStream fos = new FileOutputStream(
"src/a.txt");
DataOutputStream dos = new DataOutputStream(fos);) {
byte b = (byte)0;
for(int i=0;i<length;i++) {
dos.writeByte(b);
dos.flush();
}
while (dis.read()!= -1) {
}
} catch (IOException e) {
e.printStackTrace();
}
}
},
new Tester("Mapped RW") {
public void test() {
try (FileChannel channel = FileChannel.open(Paths.get("src/b.txt"),
StandardOpenOption.READ, StandardOpenOption.WRITE);) {
MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
for(int i=0;i<length;i++) {
mapBuffer.put((byte)0);
}
mapBuffer.flip();
while(mapBuffer.hasRemaining()) {
mapBuffer.get();
}
} catch (IOException e) {
e.printStackTrace();
}
}
},
new Tester("Mapped PRIVATE") {
public void test() {
try (FileChannel channel = FileChannel.open(Paths.get("src/c.txt"),
StandardOpenOption.READ, StandardOpenOption.WRITE);) {
MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length);
for(int i=0;i<length;i++) {
mapBuffer.put((byte)0);
}
mapBuffer.flip();
while(mapBuffer.hasRemaining()) {
mapBuffer.get();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
public static void main(String[] args) {
for(Tester tester:testers) {
tester.runTest();
}
}
}
先從整體上提一句上面的代碼,runTest()是一個模板方法,並且引用了一個未實現的test()方法;通過匿名內部類的實現,填充了測試內容。
再來說上面代碼的測試結果。用傳統流的方式,當然是最慢的,但應該是由於用的數據量是1G,無法全部讀入內存,所以它根本無法完成測試。剩下兩種MapMode.READ_WRITE
和MapMode.PRIVATE
各有特點,首先說MapMode.READ_WRITE
,它的速度每次差別較大,在0.6s和8s之間波動,而且很不穩定。但MapMode.PRIVATE
就穩得出奇,一直是1.1s到1.2s之間。但無論是哪個速度都是十分驚人的。但是MappedByteBuffer也有不足,就是在數據量很小的時候,表現比較糟糕,那是因為direct buffer的初始化時間較長,所以建議大家只有在數據量較大的時候,在用MappedByteBuffer。
還要強調的一點是,MappedByteBuffer存在內存占用和文件關閉等不確定問題。被MappedByteBuffer打開的文件只有在垃圾收集時才會被關閉,而這個點是不確定的。javadoc里是這么說的:
A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected. ——JavaDoc
關於MappedByteBuffer就告訴你這么多了,有什么問題盡管提、有什么想法隨時找我交流。