以前跟你们说过,要讲MappedByteBuffer,如今我来履行承诺了。html
首先从大致上讲一下MappedByteBuffer到底是什么。从继承结构上来说,MappedByteBuffer继承自ByteBuffer,因此ByteBuffer有的能力它全有;像变更position和limit指针啦、包装一个其余种类Buffer的视图啦,均可以。“MappedByteBuffer”为什么而来?吾辈心中亦有惑(熊猫人之谜的梗)用一个字来归纳就是快java
为何快?由于它使用direct buffer的方式读写文件内容,这种方式的学名叫作内存映射。这种方式直接调用系统底层的缓存,没有JVM和系统之间的复制操做,因此效率大大的提升了。并且因为它这么快,还能够用它来在进程(或线程)间传递消息,基本上能达到和“共享内存页”相同的做用,只不过它是依托实体文件来运行的。api
并且它还有另外一种能力。就是它可让咱们读写那些由于太大而不能放进内存中的文件。有了它,咱们就能够假定整个文件都放在内存中(实际上,大文件放在内存和虚拟内存中),基本上均可以将它看成一个特别大的数组来访问,这样极大的简化了对于大文件的修改等操做。数组
下面咱们开始介绍它的用法了缓存
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”oracle
再简单的说一下,MappedByteBuffer较之ByteBuffer新增的三个方法app
下面代码终于出场了测试
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最基本的应用,而下面这段代码主要是测试它到底有多快,this
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就告诉你这么多了,有什么问题尽管提、有什么想法随时找我交流。