C++ 文件映射:在文件内创建视图

作者: iEdon 分类: 早期归档 发布时间: 2017-01-19 16:35

前言:

通常,经过磁盘的IO是异常缓慢的,使用 ReadFile() 来读取文件效率低下。因为 ReadFile() 的工作流程是这样的:

磁盘文件(慢速) –> 拷贝至内存缓冲区 –> 业务逻辑:读取/使用(第2次拷贝)

直接使用 ReadFile() 确实方便,但当遇到超大文件业务处理或者是IO密集型业务的时候就显得力不从心了。那么,Windows 为我们提供了 FileMapping 这样一种概念(Linux 平台称作 “mmap”),来将文件直接映射至内存的某一片区域,上层应用通过内存操作,直接存取数据,其流程如下:

内存(磁盘文件)  <–内存操作–> 业务逻辑

这样一来,有效解决了IO操作的效率。这次,我们讨论在文件内建立内存映射视图。

场景:

如果我们想要浏览一个并非从文件起始位置开始的文件视图,我们首先必须要创建文件映射对象。这个对象的大小就是该文件中你想要预览的内容外加偏移大小。例如,如果我们需要预览一个文件内从第 131,072  字节(128 KB) 开始向后 1 KB 字节的内容,我们必须要创建一个至少 132,096 字节(129 KB) 的内存映射视图。这个视图从文件内第 131,072 字节(128 KB) 开始并延伸至少 1,024 字节。本实例假定操作系统文件分配颗粒为 64 KB。(实际上 Windows 系统给出的默认值便是 64 KB)

文件映射块影响着我们视图的开始位置。一个文件视图的起始位置必须是分配颗粒(64 KB)的整数倍。因此,我们想要查看的数据应该是视图内文件偏移与分配块求模。视图的大小就是数据与分配颗粒求模的偏移,外加上你想要使用的数据大小。

举个例子,我们使用 GetSystemInfo() 这样一个 API 来确定到分配颗粒的大小实际上就是 64 KB。因此,我们要通过内存映射来使用一个 138,240 字节(135 KB) 文件中的 1KB 数据,分为以下几件事:

  • 创建一个至少 139,264 字节(136 KB) 的内存映射对象。
  • 计算偏移量,文件偏移量是文件分配粒度小于所需偏移量的最大倍数。文件从偏移量开始起始。在本例中,视图从 128 KB 起始。视图的大小便是 136 KB – 128 KB (8 KB)。
  • 在视图内创建一个偏移量为 7 KB 的指针指向我们感兴趣的 1 KB 的内容。

如果我们想要访问的数据超越了分配颗粒边界,我们应该创建一个大于分配颗粒大小的视图。这避免将数据打散成零散的碎片部分。

下面是上面例子的 C++ 代码实现。

代码清单:

使用文件映射确实挺复杂,要读取数据,首先得计算各类偏移,还要判断读取位置是否越界,遇到超长文件更需要做分段映射操作。相比 ReadFile() 可是麻烦了几个数量级。但是,对于IO密集的业务来说,优质的IO操作代码,还是必要的。

参考文献:

  • MSDN,  Creating a View Within a File,  https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366548(v=vs.85).aspx