林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka
摘要:本文主要讲了java中内存映射的原理及过程,与传统IO进行了对比,最后,用实例说明了结果。
内存映射文件
内存映射文件,是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。
内存映射IO
在传统的文件IO操作中,我们都是调用操作系统提供的底层标准IO系统调用函数 read()、write() ,此时调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态,然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区,然 后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去,这样便完成了一次IO操作。这么做是为了减少磁盘的IO操作,为了提高性能而考虑的,因为我们的程序访问一般都带有局部性,也就是所 谓的局部性原理,在这里主要是指的空间局部性,即我们访问了文件的某一段数据,那么接下去很可能还会访问接下去的一段数据,由于磁盘IO操作的速度比直接 访问内存慢了好几个数量级,所以OS根据局部性原理会在一次 read()系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中,当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程私有空间,避免了再次的低 效率磁盘IO操作。其过程如下
内存映射文件和之前说的 标准IO操作最大的不同之处就在于它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一 部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。
内存映射的优缺点
万一发生了电源故障或主机故障,将会有很小的机率发生内存映射文件没有写入到磁盘,这意味着你可能会丢失关键数据。
1、传统IO读取数据,不指定缓冲区大小
/** * 传统IO读取数据,不指定缓冲区大小 * @author linbingwen * @since 2015年9月5日 * @param path * @return */ public static void readFile1(String path) { long start = System.currentTimeMillis();//开始时间 File file = new File(path); if (file.isFile()) { BufferedReader bufferedReader = null; FileReader fileReader = null; try { fileReader = new FileReader(file); bufferedReader = new BufferedReader(fileReader); String line = bufferedReader.readLine(); System.out.println("========================== 传统IO读取数据,使用虚拟机堆内存 =========================="); while (line != null) { //按行读数据 System.out.println(line); line = bufferedReader.readLine(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //最后一定要关闭 try { fileReader.close(); bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis();//结束时间 System.out.println("传统IO读取数据,不指定缓冲区大小,总共耗时:"+(end - start)+"ms"); } } }
2、传统IO读取数据,指定缓冲区大小
/** * 传统IO读取数据,指定缓冲区大小 * @author linbingwen * @since 2015年9月5日 * @param path * @return * @throws FileNotFoundException */ public static void readFile2(String path) throws FileNotFoundException { long start = System.currentTimeMillis();//开始时间 int bufSize = 1024 * 1024 * 5;//5M缓冲区 File fin = new File(path); // 文件大小200M FileChannel fcin = new RandomAccessFile(fin, "r").getChannel(); ByteBuffer rBuffer = ByteBuffer.allocate(bufSize); String enterStr = "\n"; long len = 0L; try { byte[] bs = new byte[bufSize]; String tempString = null; while (fcin.read(rBuffer) != -1) {//每次读5M到缓冲区 int rSize = rBuffer.position(); rBuffer.rewind(); rBuffer.get(bs);//将缓冲区数据读到数组中 rBuffer.clear();//清除缓冲 tempString = new String(bs, 0, rSize); int fromIndex = 0;//缓冲区起始 int endIndex = 0;//缓冲区结束 //按行读缓冲区数据 while ((endIndex = tempString.indexOf(enterStr, fromIndex)) != -1) { String line = tempString.substring(fromIndex, endIndex);//转换一行 System.out.print(line); fromIndex = endIndex + 1; } } long end = System.currentTimeMillis();//结束时间 System.out.println("传统IO读取数据,指定缓冲区大小,总共耗时:"+(end - start)+"ms"); } catch (IOException e) { e.printStackTrace(); } }
3、内存映射读文件
/** * NIO 内存映射读大文件 * @author linbingwen * @since 2015年9月15日 * @param path */ public static void readFile3(String path) { long start = System.currentTimeMillis();//开始时间 long fileLength = 0; final int BUFFER_SIZE = 0x300000;// 3M的缓冲 File file = new File(path); fileLength = file.length(); try { MappedByteBuffer inputBuffer = new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileLength);// 读取大文件 byte[] dst = new byte[BUFFER_SIZE];// 每次读出3M的内容 for (int offset = 0; offset < fileLength; offset += BUFFER_SIZE) { if (fileLength - offset >= BUFFER_SIZE) { for (int i = 0; i < BUFFER_SIZE; i++) dst[i] = inputBuffer.get(offset + i); } else { for (int i = 0; i < fileLength - offset; i++) dst[i] = inputBuffer.get(offset + i); } // 将得到的3M内容给Scanner,这里的XXX是指Scanner解析的分隔符 Scanner scan = new Scanner(new ByteArrayInputStream(dst)).useDelimiter(" "); while (scan.hasNext()) { // 这里为对读取文本解析的方法 System.out.print(scan.next() + " "); } scan.close(); } System.out.println(); long end = System.currentTimeMillis();//结束时间 System.out.println("NIO 内存映射读大文件,总共耗时:"+(end - start)+"ms"); } catch (Exception e) { e.printStackTrace(); } }
1、100M文件
文件大小如下:
调用如下:
public static void main(String args[]) { String path = "D:" + File.separator + "CES_T_MSM_LIQ-TRANS-ESP_20150702_01.DAT"; readFile1(path); //readFile2(path); //readFile3(path); }
(1)传统IO读取数据,不指定缓冲区大小,总共耗时:80264ms
其内存使用如下:
(2)传统IO读取数据,指定缓冲区大小,总共耗时:80612ms其内存使用如下:
(3)NIO 内存映射读大文件,总共耗时:90955ms其内存使用如下:
分析发现内存映射并没有比传统IO快多少,甚至还更加慢了,有可能是因为磁盘IO操作多了,反而降低了其效率,内存映射看来还是对大文件比较有好的效果。小文件基本上是没有多大的差别的。
2、1.2G文件
传统IO读取数据,不指定缓冲区大小,总共耗时:1245111ms
NIO 内存映射读大文件,总共耗时:1223877ms(大概20分钟多点)
版权声明:本文为博主林炳文Evankaka原创文章,转载请注明出处http://blog.csdn.net/evankaka
原文地址:http://blog.csdn.net/evankaka/article/details/48464013