☕【Java深层系列】「并发编程系列」深入分析和研究MappedByteBuffer的实现原理和开发指南

在Java编程语言中,操作文件IO的时候,通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,不过java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高,比起bio的模型处理方式,它大大的加大了支持解析读取文件的数量和空间。

OS的内存管理 内存层面的技术名词概念

MMU:CPU的内存管理单元。

物理内存:即内存条的内存空间。

虚拟内存:计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

页面文件:操作系统反映构建并使用虚拟内存的硬盘空间大小而创建的文件,在windows下,即pagefile.sys文件,其存在意味着物理内存被占满后,将暂时不用的数据移动到硬盘上。

缺页中断:当程序试图访问已映射在虚拟地址空间中但未被加载至物理内存的一个分页时,由MMC发出的中断。如果操作系统判断此次访问是有效的,则尝试将相关的页从虚拟内存文件中载入物理内存。

虚拟内存和物理内存

正在运行的一个进程,它所需的内存是有可能大于内存条容量之和的,如内存条是256M,程序却要创建一个2G的数据区,那么所有数据不可能都加载到内存(物理内存),必然有数据要放到其他介质中(比如硬盘),待进程需要访问那部分数据时,再调度进入物理内存,而这种场景下,被调度到硬盘的资源空间所占用的存储,我们便将他理解为虚拟内存。

MappedByteBuffer

从大体上讲一下MappedByteBuffer 究竟是什么。从继承结构上来讲,MappedByteBuffer 继承自 ByteBuffer,所以,ByteBuffer 有的能力它全有;像变动 position 和 limit 指针啦、包装一个其他种类Buffer的视图啦,内部维护了一个逻辑地址address。

“MappedByteBuffer” 会提升速度,变快

为什么快?因为它使用 direct buffer 的方式读写文件内容,这种方式的学名叫做内存映射。这种方式直接调用系统底层的缓存,没有 JVM 和系统之间的复制操作,所以效率大大的提高了。而且由于它这么快,还可以用它来在进程(或线程)间传递消息,基本上能达到和 “共享内存页” 相同的作用,只不过它是依托实体文件来运行的。

还有就是它可以让读写那些太大而不能放进内存中的文件。实现假定整个文件都放在内存中(实际上,大文件放在内存和虚拟内存中),基本上都可以将它当作一个特别大的数组来访问,这样极大的简化了对于大文件的修改等操作。

MappedByteBuffer的案例用法

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() 如果缓冲区的内容在物理内存中,则返回真,否则返回假

采用FileChannel构建相关的MappedByteBuffer //一个byte占1B,所以共向文件中存128M的数据 int length = 0x8FFFFFF; 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)); } } 实现相关的读写文件的对比处理 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(); } } } 测试结果

Stream RW->用传统流的方式,最慢,应该是由于用的数据量是 1G,无法全部读入内存,所以它根本无法完成测试。

MapMode.READ_WRITE,它的速度每次差别较大,在 0.6s 和 8s 之间波动,而且很不稳定。

MapMode.PRIVATE就稳得出奇,一直是 1.1s 到 1.2s 之间。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzdyzg.html