1. 概述

本文我们将学习 java.nio 包中的 *MappedByteBuffer*,它能帮助我们非常高效地读取文件。

2. MappedByteBuffer 是如何工作的

当我们加载文件的一个区域时,我们可以将它加载到特定内存区域,以供后面访问。

如果我们需要多次读取文件内容,这种操作代价是高昂的。为了优化,我们可以将内容保存在内存中。后续文件读取直接走主存,不需要从磁盘中加载数据,从而大大减少延迟。

需要注意的一点是,当我们使用 MappedByteBuffer 处理非常大的文件时 ,我们需要确保内存能容纳的下这个大文件

否则,我们可能会填满整个内存,导致 OutOfMemoryException 异常。 我们可以通过仅加载文件的一部分来解决这个问题。

3. 使用 MappedByteBuffer 读文件

假设我们有一个名为 fileToRead.txt 的文件,其内容如下:

This is a content of the file

该文件位于 /resource 目录中,我们可以使用下面函数加载它:

Path getFileURIFromResources(String fileName) throws Exception {
    ClassLoader classLoader = getClass().getClassLoader();
    return Paths.get(classLoader.getResource(fileName).getPath());
}

要创建 MappedByteBuffer,我们需要先创建该文件的 FileChannel。channel 创建完后,调用 map() 方法进行文件映射,需要传入3个参数:MapMode - 文件访问模式,position - 文件读取起始位置,以及 size - 指定需要读取的字节长度。

CharBuffer charBuffer = null;
Path pathToRead = getFileURIFromResources("fileToRead.txt");

try (FileChannel fileChannel (FileChannel) Files.newByteChannel(
  pathToRead, EnumSet.of(StandardOpenOption.READ))) {
 
    MappedByteBuffer mappedByteBuffer = fileChannel
      .map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());

    if (mappedByteBuffer != null) {
        charBuffer = Charset.forName("UTF-8").decode(mappedByteBuffer);
    }
}

一旦我们将文件映射到内存映射缓冲区中,我们就可以将其数据读取到 CharBuffer 中。注意,因为我们在内存中读取,而不是从磁盘,所以读的速度会非常快。

测试读到的内容是否是 fileToRead.txt 文件中的内容:

assertNotNull(charBuffer);
assertEquals(
  charBuffer.toString(), "This is a content of the file");

每次从 mappedByteBuffer 读取的数据都会非常快,因为文件的内容已映射到内存中,并且无需从磁盘中查找数据即可完成读取。

4. 使用 MappedByteBuffer 写文件

下面我们使用 MappedByteBufferfileToWriteTo.txt 写入一些内容。首先我们需要创建 FileChannel,并调用 *map()*,文件访问模式变为 FileChannel.MapMode.READ_WRITE

然后,调用 MappedByteBufferput() 方法,将 CharBuffer 内容写入到文件中。

CharBuffer charBuffer = CharBuffer
  .wrap("This will be written to the file");
Path pathToWrite = getFileURIFromResources("fileToWriteTo.txt");

try (FileChannel fileChannel = (FileChannel) Files
  .newByteChannel(pathToWrite, EnumSet.of(
    StandardOpenOption.READ, 
    StandardOpenOption.WRITE, 
    StandardOpenOption.TRUNCATE_EXISTING))) {
    
    MappedByteBuffer mappedByteBuffer = fileChannel
      .map(FileChannel.MapMode.READ_WRITE, 0, charBuffer.length());
    
    if (mappedByteBuffer != null) {
        mappedByteBuffer.put(
          Charset.forName("utf-8").encode(charBuffer));
    }
}

测试 charBuffer 实际写入的内容:

List<String> fileContent = Files.readAllLines(pathToWrite);
assertEquals(fileContent.get(0), "This will be written to the file");

5. 总结

在本快速教程中,我们学习了如何使用 java.nio 包中的 MappedByteBuffer

这是一种非常有效的多次读取文件内容的方法,因为文件被映射到内存中,后续读取不需要每次都走磁盘。

惯例,文中的示例代码可以从 GitHub 上获取。