Java 如何读取内存映射文件
通过 使用 RandomAccessFile 和 MemoryMappedBuffer ,使用内存映射 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 } } } }
Java内存映射IO
如果我们了解 java IO 在较低级别是如何工作的,那么我们就会了解缓冲区处理、内存分页和其他此类概念。
对于传统的文件 I/O,其中用户进程发出 read()
和 write()
系统调用来传输数据,几乎总是有一个或者多个复制操作来在内核空间中的这些文件系统页面之间移动数据和用户空间中的内存区域。
这是因为文件系统页面和用户缓冲区之间通常没有一对一的对齐方式。
然而,大多数操作系统都支持一种特殊类型的 I/O 操作,它允许用户进程最大程度地利用系统 I/O 面向页面的特性并完全避免缓冲区复制。
这称为内存映射 I/O,我们将在这里学习一些有关内存映射文件的知识。
内存映射文件的好处
与普通 I/O 相比,内存映射 IO 有几个优点:
- 用户进程将文件数据视为内存,因此不需要发出
read()
或者write()
系统调用。 - 当用户进程接触到映射的内存空间时,会自动产生页面错误以从磁盘中引入文件数据。如果用户修改了映射的内存空间,受影响的页面会自动标记为脏页面,随后将刷新到磁盘以更新文件。
- 操作系统的虚拟内存子系统会对页面进行智能缓存,根据系统负载自动管理内存。
- 数据总是页面对齐的,不需要缓冲区复制。
- 可以映射非常大的文件,而无需消耗大量内存来复制数据。
了解 ,并学习在 RandomAccessFile 和 MemoryMappedBuffer 的帮助下从内存映射文件中读取和写入内容。
Java 内存映射文件
内存映射 I/O 使用文件系统建立从用户空间直接到适用文件系统页面的虚拟内存映射。
使用内存映射文件,我们可以假装整个文件都在内存中,并且我们可以通过简单地将其视为一个非常大的数组来访问它。
这种方法极大地简化了我们为修改文件而编写的代码。
为了在内存映射文件中进行写入和读取,我们从一个 RandomAccessFile
开始,为该文件获取一个通道。
内存映射字节缓冲区是通过 FileChannel.map()
方法创建的。
此类使用特定于内存映射文件区域的操作扩展了 ByteBuffer
类。
映射的字节缓冲区及其表示的文件映射在缓冲区本身被垃圾收集之前一直有效。
请注意,必须在文件中指定要映射的区域的起点和长度;这意味着我们可以选择映射大文件的较小区域。
示例 1:写入内存映射文件
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("onitroad.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)。
Java 如何写入内存映射文件
使用内存映射 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("onitroad.com".getBytes()); } } }
文件映射模式
与传统的文件句柄一样,文件映射可以是可写的或者只读的。
- 前两种映射模式 MapMode.READ_ONLY 和 MapMode.READ_WRITE 相当明显。它们指示我们是希望映射为只读还是允许修改映射文件。
- 第三种模式 MapMode.PRIVATE 表示我们需要写时复制映射。这意味着我们通过
put()
所做的任何修改将导致数据的私有副本,只有MappedByteBuffer
实例可以看到。不会对底层文件进行任何更改,并且所做的任何更改都将丢失缓冲区被垃圾收集。即使写时复制映射阻止对底层文件进行任何更改,我们也必须打开文件进行读/写才能设置“MapMode.PRIVATE”映射。这对于返回的MappedByteBuffer
对象允许put()s 是必要的。
我们会注意到没有 unmap() 方法。
一旦建立,映射将一直有效,直到“MappedByteBuffer”对象被垃圾收集。
此外,映射缓冲区不绑定到创建它们的通道。
关闭关联的 FileChannel 不会破坏映射;只有处理缓冲区对象本身会破坏映射。
MemoryMappedBuffer 具有固定大小,但它映射到的文件是弹性的。
具体来说,如果在映射生效时文件的大小发生变化,则部分或者全部缓冲区可能无法访问,可能会返回未定义的数据,或者可能会引发未经检查的异常。
当文件被内存映射时,要小心其他线程或者外部进程如何操作文件。