Java中大文件读取,内存放不下怎么办?

答曰:使用内存分块映射。


但是在这之前首先看看传统的java io为什么会慢?

我们使用传统IO的时候使用:InputStream(比如FileInputStream),当我们进行对Java API Read和Write函数的调用,最终是调用JNI(Java Native Interface)的read、write系统调用。JNI,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。最终调用操作系统底层系统调用。

       而操作系统底层的read和write调用的时候,此时调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态,然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区,然后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去,也就是拷贝到了用户缓冲区,这样便完成了一次IO操作。也就是说,需要进行两次拷贝操作。而且需要频繁的从用户态切换到内核态。

有一个改进就是使用带有缓冲区的输入流(BufferedInputStream),使用时会根据情况自动为我们预读更多的字节数据到它自己维护的一个内部字节数组缓冲区中,这样我们便可以减少了用户态程序切换到内核态的次数(减少使用操作系统read的次数)。但是他还是不能解决数据从磁盘读取到内核缓冲区,然后内核缓冲区的数据复制移动到用户空间缓冲区这样移动两次的问题。程序还是需要从用户态切换到内核态。

为什么需要进行两次拷贝?

因此为了减少磁盘的IO次数,具体来说程序访问的地址一般都带有局部性,OS根据局部性原理会在一次 read()系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中,当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程私有空间,避免了再次的低效率磁盘IO操作。


什么是内存映射文件

Java NIO在此后采用了MappedByteBuffer来解决上述的问题,MappedByteBuffer是一种特殊的缓冲器,他们相比基础的 IO操作来说就是少了中间缓冲区的数据拷贝开销。同时他们属于JVM堆外内存,不受JVM堆内存大小的限。内存映射文件和之前的标准IO操作最大的不同之处就在于它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系就好像直接从内存中读、写文件一样,速度当然快了。

使用方法,内存文件映射使用:FileChannel.map()方法,这个方法直接将通道对应文件的一部分映射到内存,并返回MappedByteBuffer。map方法的第一个参数是映射模式,java中提供了3种内存映射模式,即:只读(readonly)、读写(read_write)、专用(private) 。另外两个参数是开始位置position、映射大小size(最大为Integer.MAX_VALUE)。

读写模式表明了通过内存映射文件的方式写或修改文件内容的话是会立刻反映到磁盘文件中去的,别的进程如果共享了同一个映射文件,那么也会立即看到变化!专用模式采用的是OS的“写时拷贝”原则,即在没有发生写操作的情况下,多个进程之间都是共享文件的同一块物理内存(进程各自的虚拟地址指向同一片物理地址),一旦某个进程进行写操作,那么将会把受影响的文件数据单独拷贝一份到进程的私有缓冲区中,不会反映到物理文件中去

        所以,简单来说,就是内存映射之所以比传统的IO性能要好,原因是它将文件直接映射到用户空间,减少从磁盘读到内核空间的步骤。


依然存在的问题!

但是如果读取的文件是在太大,超过了Integer.MAX_VALUE,就会出现异常,就是说利用MappedByteBuffer 来读取文件也只能最大索引到 Integer.MAX_VALUE(因为输入的参数是int类型的)。所以就需要进行多段的读取,将数据分成几段,每一段都使用内存映射来读取。这样依然比使用传统的输入流或者带缓冲的输入流要快很多。具体来说,就是计算出每一段内存映射的位置,开始和结束,然后把文件的不同段放到不同的mappedByteBuffers里面。具体的方法参考下面的网址

https://www.jianshu.com/p/87744d9eb212

 

参考:

https://blog.csdn.net/qq_21125183/article/details/88697180?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param

你可能感兴趣的:(java,java)