• 作者:老汪软件技巧
  • 发表时间:2024-11-16 15:02
  • 浏览量:

大家好,这里是小奏,觉得文章不错可以关注公众号小奏技术

java nio文件读写

我们使用java nio进行文件的读写操作时,会涉及到MappedByteBuffer、HeapByteBuffer、DirectByteBuffer这三种ByteBuffer.

我们首先看看三个buffer进行文件写入的代码

HeapByteBuffer

    private static void writeFileByHeapByteBuffer() throws Exception {
    HeapByteBuffer heapBuffer = ByteBuffer.allocate(1024);
    heapBuffer.put(MESSAGE.getBytes());
    heapBuffer.flip();
    try (RandomAccessFile raf = new RandomAccessFile(createFile("heap-file.txt"), "rw")) {
        FileChannel channel = raf.getChannel();
        int write = channel.write(heapBuffer);
    }
}

DirectByteBuffer

    private static void writeFileByDirectByteBuffer() throws Exception {
        ByteBuffer heapBuffer = ByteBuffer.allocateDirect(1024);
        heapBuffer.put(MESSAGE.getBytes());
        heapBuffer.flip();
        try (RandomAccessFile raf = new RandomAccessFile(createFile("direct-file.txt"), "rw")) {
            FileChannel channel = raf.getChannel();
            int write = channel.write(heapBuffer);
        }
    }

MappedByteBuffer

    private static void writeFileByMappedByteBuffer() throws Exception {
        try (RandomAccessFile raf = new RandomAccessFile(createFile("mapped-file.txt"), "rw")) {
            FileChannel channel = raf.getChannel();
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, MESSAGE.getBytes(StandardCharsets.UTF_8).length);
            buffer.put(MESSAGE.getBytes());
        }
    }

代码没有太多的区别,核心区别就是

HeapByteBuffer是通过ByteBuffer.allocate创建的DirectByteBuffer是通过ByteBuffer.allocateDirect创建的MappedByteBuffer是通过channel.map创建的

接下来我们看看这三个buffer在操作系统中的区别

所在区域

我们使用java nio进行文件的读写操作时,会涉及到_操作涉及读写文件会使用什么_

HeapByteBuffer

_操作涉及读写文件会使用什么_我们使用java nio进行文件的读写操作时,会涉及到

首先我们的所有内存都在操作系统的虚拟内存中。

然后JVM会向操作系统申请一大块内存。

我们创建的HeapByteBuffer也就是堆内存,是创建在JVM堆中的。属于用户空间

DirectByteBuffer

然后我们创建的DirectByteBuffer是直接内存,是创建在操作系统的内存中的,不属于JVM内存。但是也属于用户空间

MappedByteBuffer

MappedByteBuffer也是在操作系统内存中,不同于DirectByteBuffer的是MappedByteBuffer是直接映射到pageCache。所以算是直接写内核态的pageCache,不像DirectByteBuffer需要拷贝到pageCache,还要进行上下文切换

现在知道了这三种buffer的所在区域,我们来看看他们是如何读写的,然后聊聊会涉及几次上下文切换、几次数据拷贝

数据读写过程

操作涉及读写文件会使用什么_我们使用java nio进行文件的读写操作时,会涉及到_

先上一张图,然后我们来具体分析

HeapByteBuffer

HeapByteBuffer由于是在JVM中,所以首先要通过IOUtil临时拷贝到一个临时的堆外内存DirectByteBuffer中

代码里面有判断,具体我们可以看源码

我们使用java nio进行文件的读写操作时,会涉及到_操作涉及读写文件会使用什么_

这里为什么要拷贝一次呢?因为HeapByteBuffer是在JVM中的,受GC影响,GC会移动内存

导致HeapByteBuffer 在 GC 之后它背后的内存地址可能已经发生了变化。所以不能直接操作HeapByteBuffer,需要拷贝到一个临时的堆外内存DirectByteBuffer中

读文件用户调用read方法进行文件读取,JVM进程会进行一次上下文切换进入内核空间内核空间查看pagecache是否有文件数据,如果有,则直接将文件通过CPU拷贝到DirectByteBuffer中,然后由DirectByteBuffer拷贝到HeapByteBuffer中。 这里涉及两次数据拷贝,一次上下文切换如果pageche没有数据,则通过DMA拷贝将磁盘的数据拷贝到pagecache中

所以如果文件没有命中pagecache。

总过涉及3次数据拷贝,2次上下文切换

磁盘数据通过DMA拷贝到pagecache中pagecache中的数据通过CPU拷贝到DirectByteBuffer中DirectByteBuffer中的数据拷贝到HeapByteBuffer中

上下文切换就进入内核一次,出内核一次。共两次

写文件

所以整体的流程是

在JVM分配一个堆内存HeapByteBuffer将HeapByteBuffer拷贝到一个临时的堆外内存DirectByteBuffer, 进行一次数据拷贝上下文切换进入到内核空间,同时这里进行一次CPU拷贝,将DirectByteBuffer拷贝到pageCache中pageCache再通过DMA拷贝到磁盘中,再次进行数据拷贝然后再次上下文切换回到用户空间

所以HeapByteBuffer进行文件写入的时候涉及3次数据拷贝,2次上下文切换

DirectByteBuffer

DirectByteBuffer相比HeapByteBuffer流程相同,主要是少了JVM堆内存拷贝到临时的堆外内存DirectByteBuffer这一步

MappedByteBuffer读取文件MappedByteBuffer初次创建需要将文件的某个区域映射到 JVM 进程的虚拟内存空间中,从而获得一段文件映射的虚拟内存区域 MappedByteBuffer所以在初始化的时候就会进行两次上下文切换第一次对MappedByteBuffer进行读取时,会触发缺页中断,内核将文件的内容从磁盘加载到pageCache中.由于这里涉及到了缺页中断的处理,因此也会有两次上下文切换的开销,同时这里是由于DMA将磁盘的数据拷贝到pageCache中,所以也会有一次数据拷贝

所以整个过程是4次上下文切换,1次数据拷贝

但是实际我们在计算MappedByteBuffer的时候,主要是计算MappedByteBuffer已经文件预热了。

完整的文件读取流程是4次上下午切换、一次数据拷贝(DMA拷贝),少了CPU拷贝

写入文件MappedByteBuffer初次创建需要将文件的某个区域映射到 JVM 进程的虚拟内存空间中,从而获得一段文件映射的虚拟内存区域 MappedByteBuffer所以在初始化的时候就会进行两次上下文切换第一次对MappedByteBuffer进行写入时候直接写入pageCache,然后通过DMA拷贝到磁盘中,所以只有一次数据拷贝总结

可以看到如果MappedByteBuffer进行文件映射后,不需要cpu拷贝,不需要上下文切换

这么看MappedByteBuffer好像最牛逼,还要DirectByteBuffer干嘛,全部无脑用MappedByteBuffer进行文件读写不就好了?

实际MappedByteBuffer受缺页中断的性能影响,并不是一定是性能最优的

实际的性能还是看具体文件写入的大小

实际生产使用可以多做一些性能测试,也有博主对MappedByteBuffer和DirectByteBuffer对不同文件的读写做过性能测试,感兴趣的可以自己去看看

参考