-
内存映射文件说明
Java在处理大文件时,一般使用BufferedReader,BufferedInputStream这里带缓冲的IO类,如果是超大的文件的话,更快的方式是使用MappedByteBuffer。
MappedByteBuffer是NIO引入的文件内存映射方案,读写性能极高。
MappedByteBuffer是一种特殊的ByteBuffer,即是ByteBuffer的子类。
ByteBuffer有两种模式:直接和间接。
间接模式最典型的就是HeapByteBuffer,即操作堆内存。但是内存毕竟有限,如果要发送一个1G大小的文件,这时在堆中分配这么大的一个内存空间是不现实的。此时就要使用直接模式了,即MappedByteBuffer,文件映射。
这里先说一下操作系统的内存管理。一般操作系统的内存分为两部分:物理内存和虚拟内存。
虚拟内存一般使用的是页面映像文件,即磁盘中的某个特殊的文件。操作系统负责读写该文件,这个读写的过程叫"页面中断/切换"。
MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个MappedByteBuffer。MappedByteBuffer将文件直接映射到虚拟内存中。如果文件太大的话也可以分段映射,只要指定文件的那个部分就可以。
-
MappedByteBuffer说明
FileChannel提供了map方法来把文件映射为内存映像文件:
MappedByteBuffer map(int mode,long position,long size) 方法可以把文件的从position开始的size大小的区域映射为内存映像文件。
mode参数指出了可以访问该内存映像文件的方式:
- MapMode.READ_ONLY(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException。
- MapMode.READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。
- MapMode.PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。
MappedByteBuffer是ByteBuffer的子类,其扩充了三个方法:
- force():缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
- load():将缓冲区的内容载入内存,并返回该缓冲区的引用
- isLoaded():如果缓冲区的内容在物理内存中,则返回真,否则返回假
-
案例对比
这里通过采用ByteBuffer和MappedByteBuffer分别读取两种大小的文件,来比较两者之间的区别。
代码
public class MappedByteBufferTest { public static void main(String[] args) { String file = ""; f_ByteBuffer(file); System.out.println("=================="); f_MappedByteBuffer(file); } /** * ByteBuffer读取文件 */ public static void f_ByteBuffer(String file){ RandomAccessFile accessFile = null; FileChannel fc = null; try { accessFile = new RandomAccessFile(file,"rw"); fc = accessFile.getChannel(); long startTime = System.currentTimeMillis(); ByteBuffer byteBuffer = ByteBuffer.allocate((int)accessFile.length()); byteBuffer.clear(); fc.read(byteBuffer); long endTime = System.currentTimeMillis(); System.out.println("ByteBuffer读取耗时:" + (endTime - startTime)); }catch (IOException e){ e.printStackTrace(); }finally { try { if(accessFile != null){ accessFile.close(); } if(fc != null){ fc.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * MappedByteBuffer读取文件 */ public static void f_MappedByteBuffer(String file){ RandomAccessFile accessFile = null; FileChannel fc = null; try { accessFile = new RandomAccessFile(file,"rw"); fc = accessFile.getChannel(); long startTime = System.currentTimeMillis(); MappedByteBuffer map = fc.map(FileChannel.MapMode.READ_ONLY, 0, accessFile.length()); long endTime = System.currentTimeMillis(); System.out.println("MappedByteBuffer读取耗时:" + (endTime - startTime)); }catch (IOException e){ e.printStackTrace(); }finally { try { if(accessFile != null){ accessFile.close(); } if(fc != null){ fc.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
读一个5M左右的文件,运行结果
读一个1G左右的文件,运行结果
注意:MappedByteBuffer有资源释放的问题:被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,而这个点是不确定的