使用内存映射文件读写大文件

使用内存映射文件读写大文件

文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类。一般来说,这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。使用字符串变量的方法不仅会加重内存的负担,而且会Unicode和ASCII码的转换会把你弄得焦头烂额。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,比I/O读写要快20倍,所谓I/O操作不是对外围设备直接进行操作,而是对设备与cpu连接的接口电路的操作。而映射文件的方法就是对磁盘直接进行操作。

内存映射文件无非就是那些文件中的数据被直接映射到进程地址空间中去的文件,与虚拟内存有类似的地方是,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外,实际工程中的系统往往需要在多个进程之间共享数据,如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。

首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。

CreateFileMapping()在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间,实际上相当于加载文件中指定的数据到内存中。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。

实际上操作文件映射对象就相当于操作VC++文件读写方式下的文件内部指针。

而在某些特殊行业,经常要面对十几GB乃至几十GB容量的巨型文件,而一个32位进程所拥有的虚拟地址空间只有2^32 = 4GB,显然不能一次将文件映像全部映射进来。对于这种情况只能依次将大文件的各个部分映射到进程中的一个较小的地址空间。这需要对上面的一般流程进行适当的更改:

1)映射从文件开头的映像;

2)对该映像进行访问;

3)取消此映像;

4)映射一个从文件中的一个更深的位移开始的新映像;

5)重复步骤2,直到访问完全部的文件数据。

示例代码:

在本例中,首先通过GetFileSize()得到被处理文件长度(64位)的高32位和低32位值。然后在映射过程中设定每次映射的块大小为1000倍的分配粒度(系统的数据分块大小),如果文件长度小于1000倍的分配粒度时则将块大小设置为文件的实际长度。在处理过程中由映射、访问、撤消映射构成了一个循环处理。其中,每处理完一个文件块后都通过关闭文件映射对象来对每个文件块进行整理。CreateFileMapping()、MapViewOfFile()等函数是专门用来进行内存文件映射处理用的。

 // 创建文件对象

 HANDLE hFile = ::CreateFile(strFile, GENERIC_READ,FILE_SHARE_READ, NULL,

  OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);

 if (hFile == INVALID_HANDLE_VALUE)

 {

  TRACE("创建文件对象失败,错误代码:%d\r\n", GetLastError());

  return;

 }

 // 创建文件映射对象

 HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

 if (hFileMap == NULL)

 {

  TRACE("创建文件映射对象失败,错误代码:%d\r\n", GetLastError());  

  return;

 }

 // 得到系统分配粒度

 SYSTEM_INFO SysInfo;

 GetSystemInfo(&SysInfo);

 DWORD dwGran = SysInfo.dwAllocationGranularity;

 // 得到文件尺寸

 DWORD dwFileSizeHigh;

 __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);

 qwFileSize |= (((__int64)dwFileSizeHigh) << 32);///MSDN

 // 偏移地址

 __int64 qwFileOffset = 0;

 __int64 T_newmap = 900 * dwGran;

 // 块大小

 DWORD dwBlockBytes = 1000 * dwGran;//文件数据分段大小

 if (qwFileSize - qwFileOffset < dwBlockBytes)

  dwBlockBytes = (DWORD)qwFileSize;

 // 映射视图

 char *lpbMapAddress = (char *)MapViewOfFile(hFileMap,FILE_MAP_READ,

  (DWORD)(qwFileOffset >> 32), (DWORD)(qwFileOffset & 0xFFFFFFFF),dwBlockBytes);

 if (lpbMapAddress == NULL)

 {

  TRACE("映射文件映射失败,错误代码:%d ", GetLastError());

  return;

 } 

 // 关闭文件对象

 CloseHandle(hFile); 

 ///读文件数据

 while(qwFileOffset < qwFileSize)

 {

  /********************            读文件             ***************************/  

  //read_eh(&lpbMapAddress)读取已映射到内存的数据,并将文件指针作相应后移(lpbMapAddress++),返回指针偏移量

  qwFileOffset = qwFileOffset + read_eh(&lpbMapAddress); //修改偏移量

  if (qwFileOffset > T_newmap)

  {//当数据读到90%时,为防数据溢出,需要映射在其后的数据  T_newmap

   UnmapViewOfFile(lpbMapAddress);//释放当前映射

   if ((DWORD)(qwFileSize - T_newmap) < dwBlockBytes)

   dwBlockBytes = (DWORD)(qwFileSize - T_newmap);

   lpbMapAddress = (char *)MapViewOfFile(hFileMap,FILE_MAP_READ,

   (DWORD)(T_newmap >> 32), (DWORD)(T_newmap & 0xFFFFFFFF),dwBlockBytes);

   // 修正参数

   lpbMapAddress = lpbMapAddress + qwFileOffset - T_newmap;

   T_newmap =T_newmap  + 900 * dwGran;

   if (lpbMapAddress == NULL)

   {

    TRACE("映射文件映射失败,错误代码:%d ", GetLastError());

    return;

   }

  }

 }

 //释放最后数据块映射

 UnmapViewOfFile(lpbMapAddress);

 // 关闭文件映射对象句柄

 CloseHandle(hFileMap); 


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 VC 中使用内存映射技术可以显著提高对大文件的读写速度。下面是一个简单的示例代码: ```c++ #include <iostream> #include <windows.h> using namespace std; int main() { HANDLE hFile; HANDLE hMapping; LPVOID lpMapping; // 打开文件 hFile = CreateFile("largefile.bin", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { cout << "Failed to open file." << endl; return 1; } // 创建文件映射 hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL); if (hMapping == NULL) { cout << "Failed to create file mapping." << endl; CloseHandle(hFile); return 1; } // 映射文件到内存 lpMapping = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (lpMapping == NULL) { cout << "Failed to map file to memory." << endl; CloseHandle(hMapping); CloseHandle(hFile); return 1; } // 读写文件 char* pFile = (char*)lpMapping; for (int i = 0; i < 4 * 1024 * 1024 * 1024LL; i++) { pFile[i] = i % 256; } // 解除内存映射 UnmapViewOfFile(lpMapping); // 关闭文件映射和文件句柄 CloseHandle(hMapping); CloseHandle(hFile); cout << "Done." << endl; return 0; } ``` 这个示例程序打开一个名为 largefile.bin 的文件,并将其映射到内存中。然后,程序将对文件进行读写操作,最后解除内存映射并关闭文件。在这个示例中,我们使用了一个 4GB 的文件进行测试,程序可以在较短的时间内完成读写操作,而不会出现内存不足的错误。 需要注意的是,内存映射虽然可以提高文件读写速度,但也会占用一定的内存空间。因此,在使用内存映射技术时,需要根据实际情况选择合适的文件大小和内存使用量。同时,需要注意内存映射可能会对程序的稳定性产生影响,特别是在进行大量读写操作时。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值