先说结论:使用内存映射文件来处理大文件可以提高效率。
为什么呢?
我们先来看看如果不使用内存映射文件的处理流程是怎样的,首先我们得先读出磁盘文件的内容到内存中,然后修改,最后回写到磁盘上。第一步读磁盘文件是要经过一次系统调用的,它首先将文件内容从磁盘拷贝到内核空间的一个缓冲区,然后再将这些数据拷贝到用户空间,实际上是两次数据拷贝。第三步回写也一样也要经过两次数据拷贝。
所以我们基本上会有四次数据的拷贝了,因为大文件数据量很大,几十GB甚至更大,所以拷贝的开销是非常大的。
而内存映射文件是操作系统的提供的一种机制,可以减少这种不必要的数据拷贝,从而提高效率。它由mmap()将文件直接映射到用户空间,mmap()并没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,所以只进行了一次数据拷贝
,比read进行两次数据拷贝要好上一倍,因此,内存映射的效率要比read/write效率高。
一般来说,read write操作可以满足大多数文件操作的要求,但是对于某些特殊应用领域所需要的几十GB甚至更大的存储,这种通常的文件处理方法进行处理显然是行不通的。目前,对于上述大文件的操作一般是以内存映射文件的方式来加以处理的。
内存映射,并不是将文件加载到内存。
内存映射首先申请一段地址空间,并映射到物理存储器,而这里的物理存储器就是文件所在的磁盘,类似虚拟内存(pagefile);
当有需要时,程序不需要先把它加到内存,而是直接从磁盘读取。从这里看,IO操作减少了(不需要先加载到内存)
使用内存映射文件,读取大文件
public static void main(String[] args) throws IOException {
List<String> res = new ArrayList<>();
File file = new File("C:\\Users\\7q\\Desktop\\LogStat_2017-05-15_000.log");
long length = file.length();
//new RandomAccessFile(file,mode);只读方式("r"),还是以读写方式("rw"),不只是只写方式
//fileChannel.map(FileChannel.MapMode mode, long position, long size),映射方式,从什么位置开始映射,映射的范围有多大
MappedByteBuffer buffer = new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < (int) length; i++) {
//buffer.get(i):返回对应索引的字节 \n的ASCII为10
if (buffer.get(i) == 10) {//判断遇到换行符,处理此行数据
res.add(sb.toString());
sb.delete(0, sb.length());
} else if (i == length - 1) {//判断到了最后一行,处理此行数据
sb.append((char) buffer.get(i));
res.add(sb.toString());
sb.append((char) buffer.get(i));
} else {//拼接成一行数据
sb.append((char) buffer.get(i));
}
}
for (String re : res) {
System.out.println(re);
}
}