Java NIO 之 Java内存映射文件(Java MappedByteBuffer)

了解有关Java内存映射文件的信息,并在RandomAccessFile和MemoryMappedBuffer的帮助下学习从内存映射文件读取和写入内容。

1.内存映射的IO

如果您知道java IO在较低级别如何工作,那么您将了解缓冲区处理,内存分页和其他此类概念。对于传统的文件I / O,用户进程在其中进行发布read()并进行write()系统调用以传输数据,几乎总是存在一个或多个复制操作,以在内核空间中的这些文件系统页面和用户空间中的存储区域之间移动数据。这是因为文件系统页面和用户缓冲区之间通常没有一对一的对齐方式。

但是,大多数操作系统都支持一种特殊类型的I / O操作,它允许用户进程最大程度地利用系统I / O的面向页面的特性,并完全避免缓冲区复制。这称为内存映射I / O,我们将在此处学习有关内存映射文件的一些知识。

2. Java内存映射文件

内存映射的I / O使用文件系统来建立从用户空间直接到适用文件系统页面的虚拟内存映射。使用内存映射文件,我们可以假装整个文件都在内存中,并且可以通过简单地将其视为非常大的数组来访问它。这种方法极大地简化了我们为修改文件而编写的代码。

阅读更多:使用缓冲区

要在内存映射文件中进行写入和读取,我们从开始RandomAccessFile,获取该文件的通道。通过该FileChannel.map()方法创建内存映射的字节缓冲区。ByteBuffer该类通过特定于内存映射文件区域的操作扩展了该类。

映射的字节缓冲区及其表示的文件映射将保持有效,直到缓冲区本身被垃圾回收为止。请注意,必须指定文件中要映射的区域的起点和长度。这意味着您可以选择映射大文件的较小区域。

MemoryMappedFileExample.java
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMappedFileExample
{
    static int length = 0x8FFFFFF
    public static void main(String[] args) throws Exception
    {
        try(RandomAccessFile file = new RandomAccessFile("howtodoinjava.dat", "rw"))
        {
            MappedByteBuffer out = file.getChannel()
                                        .map(FileChannel.MapMode.READ_WRITE, 0, length);
            
            for (int i = 0; i < length; i++)
            {
                out.put((byte) 'x');
            }
            
            System.out.println("Finished writing");
        }
    }
}

使用上述程序创建的文件的长度为128 MB,可能大于操作系统允许的空间。该文件似乎可以一次访问,因为只有部分文件被带入内存,而其他部分被换出。这样,可以轻松地修改非常大的文件(最大2 GB)。

文件模式

像常规文件句柄一样,文件映射可以是可写的或只读的。

  • 前两个映射模式MapMode.READ_ONLYMapMode.READ_WRITE相当明显。它们指示您是要映射为只读映射还是允许修改映射文件。
  • 第三种模式MapMode.PRIVATE表示您需要写时复制映射。这意味着您进行的任何修改都put()将导致仅MappedByteBuffer实例可见的数据的私有副本。不会对基础文件进行任何更改,并且对缓冲区进行垃圾回收时所做的任何更改都将丢失。即使写时复制映射阻止对基础文件的任何更改,您也必须已打开文件以进行读/写操作以建立MapMode.PRIVATE映射。这对于返回的MappedByteBuffer对象允许放置put()是必要的。

您会注意到没有unmap()方法。建立后,映射将一直有效,直到MappedByteBuffer对象被垃圾回收为止。同样,映射缓冲区不绑定到创建它们的通道。关闭关联的FileChannel不会破坏映射。仅处理缓冲区对象本身会破坏映射。

MemoryMappedBuffer具有固定大小,但是映射到的文件是弹性的。具体来说,如果在映射生效时文件的大小发生变化,则某些或所有缓冲区可能变得不可访问,可能会返回未定义的数据,或者会引发未经检查的异常。内存映射文件时,请注意其他线程或外部进程如何处理文件。

3.内存映射文件的好处

与常规I / O相比,内存映射IO具有以下优点:

  1. 用户进程将文件数据视为内存,因此无需发出read()write()系统调用。
  2. 当用户进程触摸映射的内存空间时,将自动生成页面错误,以从磁盘引入文件数据。如果用户修改了映射的内存空间,则受影响的页面会自动标记为脏页面,随后将刷新到磁盘以更新文件。
  3. 操作系统的虚拟内存子系统将执行页面的智能缓存,并根据系统负载自动管理内存。
  4. 数据总是页面对齐的,不需要缓冲区复制。
  5. 可以映射非常大的文件,而无需消耗大量内存来复制数据。

4.如何读取内存映射文件

要使用内存映射的IO读取文件,请使用以下代码模板:

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMappedFileReadExample
{
    private static String bigExcelFile = "bigFile.xls";
    public static void main(String[] args) throws Exception
    {
        try (RandomAccessFile file = new RandomAccessFile(new File(bigExcelFile), "r"))
        {
            //Get file channel in read-only mode
            FileChannel fileChannel = file.getChannel();
            
            //Get direct byte buffer access using channel.map() operation
            MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
            
            // the buffer now reads the file as if it were loaded in memory.
            System.out.println(buffer.isLoaded());  //prints false
            System.out.println(buffer.capacity());  //Get the size based on content size of file
            
            //You can read the file from this buffer the way you like.
            for (int i = 0; i < buffer.limit(); i++)
            {
                System.out.print((char) buffer.get()); //Print the content of file
            }
        }
    }
}

5.如何写入内存映射文件

要使用内存映射的IO将数据写入文件,请使用以下代码模板:

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMappedFileWriteExample {
    private static String bigTextFile = "test.txt";
    public static void main(String[] args) throws Exception
    {
        // Create file object
        File file = new File(bigTextFile);
        
        //Delete the file; we will create a new file
        file.delete();
                    
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"))
        {
            // Get file channel in read-write mode
            FileChannel fileChannel = randomAccessFile.getChannel();
            // Get direct byte buffer access using channel.map() operation
            MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096 * 8 * 8);
            //Write the content using put methods
            buffer.put("how2codex.com".getBytes());
        }
    }
}

在评论部分中将您的评论和想法留给我。

学习愉快!

This entry was posted in   Nio.
Bookmark the   permalink.

saigon has written 1440 articles

Leave a Reply