首页 > 代码库 > 【JavaNIO的深入研究4】内存映射文件I/O

【JavaNIO的深入研究4】内存映射文件I/O

内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问。这种解决办法能大大简化修改文件的代码。
fileChannel.map(FileChannel.MapMode mode, long position, long size)将此通道的文件区域直接映射到内存中。注意,你必须指明,它是从文件的哪个位置开始映射的,映射的范围又有多大;也就是说,它还可以映射一个大文件的某个小片断。


MappedByteBuffer是ByteBuffer的子类,因此它具备了ByteBuffer的所有方法,但新添了force()将缓冲区的内容强制刷新到存储设备中去、load()将存储设备中的数据加载到内存中、isLoaded()位置内存中的数据是否与存储设置上同步。这里只简单地演示了一下put()和get()方法,除此之外,你还可以使用asCharBuffer( )之类的方法得到相应基本类型数据的缓冲视图后,可以方便的读写基本类型数据。

 

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。

内存映射文件 I/O 是通过使文件中的数据神奇般地出现为内存数组的内容来完成的。这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。一般来说,只有文件中实际读取或者写入的部分才会送入(或者 映射 )到内存中。

内存映射并不真的神奇或者多么不寻常。现代操作系统一般根据需要将文件的部分映射为内存的部分,从而实现文件系统。Java 内存映射机制不过是在底层操作系统中可以采用这种机制时,提供了对该机制的访问。

尽管创建内存映射文件相当简单,但是向它写入可能是危险的。仅只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。

 

(1)了解内存映射的最好方法是使用例子。在下面的例子中,我们要将一个 FileChannel (它的全部或者部分)映射到内存中。为此我们将使用 FileChannel.map() 方法。下面代码行将文件的前 1024 个字节映射到内存中:

public class UseMappedFile
{
  static private final int start = 0;
  static private final int size = 1024;

  static public void main( String args[] ) throws Exception {
    RandomAccessFile raf = new RandomAccessFile( "usemappedfile.txt", "rw" );
    FileChannel fc = raf.getChannel();

    MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,
      start, size );

    mbb.put( 0, (byte)97 );
    mbb.put( 1023, (byte)122 );
    for (int i = start; i < size; i++) {  
         System.out.print((char) mbb.get(i));  
    }  
    raf.close();
  }
}

运行结果:

技术分享

技术分享

(2)该程序创建了一个128Mb的文件,如果一次性读到内存可能导致内存溢出,但这里访问好像只是一瞬间的事,这是因为,真正调入内存的只是其中的一小部分,其余部分则被放在交换文件上。这样你就可以很方便地修改超大型的文件了(最大可以到2 GB)。注意,Java是调用操作系统的"文件映射机制"来提升性能的。

public class LargeMappedFiles {  
    static int length = 0x8000000; // 128 Mb  
  
    public static void main(String[] args) throws Exception {  
        // 为了以可读可写的方式打开文件,这里使用RandomAccessFile来创建文件。  
        FileChannel fc = new RandomAccessFile("test.dat", "rw").getChannel();  
        //注意,文件通道的可读可写要建立在文件流本身可读写的基础之上  
        MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);  
        //写128M的内容  
        for (int i = 0; i < length; i++) {  
            out.put((byte) ‘x‘);  
        }  
        System.out.println("Finished writing");  
        //读取文件中间6个字节内容  
        for (int i = length / 2; i < length / 2 + 6; i++) {  
            System.out.print((char) out.get(i));  
        }  
        fc.close();  
    }  
}  

运行结果:

Finished writing
xxxxxx

 (3)java处理大文件,一般用BufferedReader,BufferedInputStream这类带缓冲的Io类,不过如果文件超大的话,更快的方式是采用MappedByteBuffer。

三种方式:
              FileChannel提供了map方法来把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式:READ_ONLY,READ_WRITE,PRIVATE.       

             
a. READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)


 b. READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)


c. PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)

三个方法:

a. fore();缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
b. load()将缓冲区的内容载入内存,并返回该缓冲区的引用
c. isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假

三个特性:

    调用信道的map()方法后,即可将文件的某一部分或全部映射到内存中,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点:

a. 读取快
b. 写入快
c. 随时随地写入

package study;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.nio.ByteBuffer;  
import java.nio.MappedByteBuffer;  
import java.nio.channels.FileChannel;  
  
public class MapMemeryBuffer {  
  
    public static void main(String[] args) throws Exception {  
        ByteBuffer byteBuf = ByteBuffer.allocate(1024 * 14 * 1024);  
        byte[] bbb = new byte[14 * 1024 * 1024];  
        FileInputStream fis = new FileInputStream("e://data/other/UltraEdit_17.00.0.1035_SC.exe");  
        FileOutputStream fos = new FileOutputStream("e://data/other/outFile.txt");  
        FileChannel fc = fis.getChannel();  
        long timeStar = System.currentTimeMillis();// 得到当前的时间  
        fc.read(byteBuf);// 1 读取  
        //MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());  
        System.out.println(fc.size()/1024);  
        long timeEnd = System.currentTimeMillis();// 得到当前的时间  
        System.out.println("Read time :" + (timeEnd - timeStar) + "ms");  
        timeStar = System.currentTimeMillis();  
        fos.write(bbb);//2.写入  
        //mbb.flip();  
        timeEnd = System.currentTimeMillis();  
        System.out.println("Write time :" + (timeEnd - timeStar) + "ms");  
        fos.flush();  
        fc.close();  
        fis.close();  
    }  
  
}  
运行结果:  
14235  
Read time :24ms  
Write time :21ms  
我们把标注1和2语句注释掉,换成它们下面的被注释的那条语句,再来看运行效果。14235  
Read time :2ms  
Write time :0ms  

可以看出速度有了很大的提升。MappedByteBuffer的确快,但也存在一些问题,主要就是内存占用和文件关闭等不确定问题。被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,而这个点是不确定的。在javadoc里是这么说的:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself  is garbage-collected.

转自:http://langgufu.iteye.com/blog/2107023

【JavaNIO的深入研究4】内存映射文件I/O