内存映射文件

1. 前言

在处理大文件时,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 来进行频繁的读写操作,都将导致进程因频繁读写外存而降低速度。

  • 内存映射文件允许Java程序直接从内存中读取文件内容,通过将整个或部分文件映射到内存,由操作系统来处理加载请求和写入文件,应用只需要和内存打交道,这使得IO操作非常快。加载内存映射文件所使用的内存在Java堆区之外。Java编程语言支持内存映射文件,通过java.nio包和MappedByteBuffer 可以从内存直接读写文件。
  • 内存映射IO最大的优点可能在于性能,这对于建立高频电子交易系统尤其重要。内存映射文件通常比标准通过正常IO访问文件要快。另一个巨大的优势是内存映 射IO允许加载不能直接访问的潜在巨大文件 。经验表明,内存映射IO在大文件处理方面性能更加优异。尽管它也有不足——增加了页面错误的数目。由于操作系统只将一部分文件加载到内存,如果一个请求 页面没有在内存中,它将导致页面错误。同样它可以被用来在两个进程中共享数据。
  • 大多数主流操作系统比如Windows平台,UNIX,Solaris和其他类UNIX操作系统都支持内存映射IO和64位架构,几乎可以将所有文件映射到内存并通过JAVA编程语言直接访问。

2. 四种速度测试方案:

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class Test {

    public static void main(String[] args) {  
    //通过FileInputStream流读取
        try {  
            FileInputStream fis=new FileInputStream("/home/tobacco/test/res.txt");  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            try {  
                while((n=fis.read())>=0){  
                    sum+=n;  
                }  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
      //通过用BufferedInputStream包装的FileInputStream流读取
        try {  
            FileInputStream fis=new FileInputStream("/home/tobacco/test/res.txt");  
            BufferedInputStream bis=new BufferedInputStream(fis);  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            try {  
                while((n=bis.read())>=0){  
                    sum+=n;  
                }  
            } catch (IOException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
      //通过内存映射方式读取
        MappedByteBuffer buffer=null;  
        try {  
            buffer=new RandomAccessFile("/home/tobacco/test/res.txt","rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1253244);  
            int sum=0;  
            int n;  
            long t1=System.currentTimeMillis();  
            for(int i=0;i<1253244;i++){  
                n=0x000000ff&buffer.get(i);  
                sum+=n;  
            }  
            long t=System.currentTimeMillis()-t1;  
            System.out.println("sum:"+sum+"  time:"+t);  
        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
  
    }  
} 

3. 内存映射使用步骤:

3.1. 1、建立通道:

3.1.1.1.1. 通过FileChannel的静态方法:

static FileChannel open​(Path path, OpenOption... options)

3.1.1.1.2. 通过FileChannel的静态方法:

static FileChannel open​(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)

3.1.1.1.3. FileInputStream类的方法:

FileChannel getChannel()

3.2. 2、从通道将文件映射到内存缓冲区:

FileChannel类的方法:

abstract MappedByteBuffer map​(FileChannel.MapMode mode, long position, long size)

FileChannel.MapMode是一个内部静态类。

类型 字段 说明
static FileChannel.MapMode PRIVATE Mode for a private (copy-on-write) mapping.
static FileChannel.MapMode READ_ONLY Mode for a read-only mapping.
static FileChannel.MapMode READ_WRITE Mode for a read/write mapping.

3.3. 3、从缓冲区读取数据

使用ByteBuffer的方法:

  • get() getChar()读取; 用hasremaining()判断结尾等。