怎样编程实现读写大型文件

(yanlb2000, 2007.01.03, yanlb2000.blogcn.com)

在很多应用中,我们需要对文件内容进行读写等操作。一般,我们用ReadFile, WriteFile这两个API函数就可以了。


然而,在一些场合,可能需要对大量文件进行全面的读写操作。比如,对硬盘上的文件作全文检索、索引,或者编制HASH列表等。这些情况下,文件的读写操作量可能是非常大的。而且,有些文件论个头也是非常巨大,比如CD、DVD光盘镜像、高清视频节目等,动辄几百MB、甚至几个、十几个GB。这些,都对文件处理算法提出了一定的要求:要求能快速高效又准确地处理大数据量的文件。

这里,其实有两个要求:

1, 正确。显然,这是基本的要求。如果文件读写过程是错误的或有隐含错误的,那么基本功能都不能正确完成,其他就免谈了。

2, 快速。读写文件的方法不少,但怎样才能更快呢?而且是要在保证正确的前提下。

 

对于上面的第一点,即正确性,要补充说明一下。多数软件在多数情况下处理文件都是正确的。但是,如果要处理一个大于2GB或者4GB的文件,有些软件就不行了,或者报错,或者结果是错误的。这是因为,目前主流编程环境还是基于32位字长的CPU和操作系统环境的,一个普通指针、整数都表达2^32或其一半(当作为无符号或有符号整数时),即4GB或者2GB。然而,这些软件却没有考虑到如果文件大于4GB的情况,仍然使用普通的整数或指针来表达文件的大小或者指针,这当然就不能正确处理大于4GB的文件了。其实这时候,就必须用两个32位整数来组合表达一个64位的变量,来完成原有的单个32位整数所不能表达的大小。(不过,在64位编程环境下,这个问题就容易多了,因为一个整数或指针“自然地”就是64位的。)当然,即使是32位Windows环境下,Win32 API中这些文件读写相关的函数,也都是支持这种大数表示的,否则也就不能支持NTFS上大于4GB的文件了。

 

再说说第二点,要快速。这里,我就要推荐本文的主角了:MapViewOfFile。该函数可以将一个文件的内容映射到运行进程的地址空间中,以后的所有对文件的操作,就可以直接象对内存一样直接访问了。这样,我们就不需要再显示调用繁琐的文件IO操作函数了。这一切读写操作,都由系统内核帮助我们自动完成。另外,文件读写性能也会有很大提高,特别是系统内存较大、要读写的文件体积又很大的时候,性能优势会很明显。

具体的比较,我这里就不谈了,大家可以查找网上的资料。比如,这里有一个网页:


该网友对MapViewOfFile和ReadFile进行了比较,其操作是实现将一个文件进行头尾内容的倒序。小文件比较相差不大,但对于一个64MB的文件,使用MapViewOfFile和ReadFile相比,速度差别几乎有一个数量级(10倍)。那么,对于一个几百MB、几个GB的文件呢?显然会有更明显的效率差别。

不过,附带说明的是,该网页代码中,就没有应对大型文件的处理。所以,这个代码是不能处理大于4GB的文件的。

 

 

我写了一个计算文件MD5摘要的软件,其中就用了MapViewOfFile。这里将文件读写部分写在这里,供大家参考。

关于MapViewOfFile的更多讨论,我可能会准备再写篇文章来专门讨论。这里,就仅仅给出通过MapViewOfFile来读写大型文件的方法的通用流程。 

//生成一个文件的MD5摘要,N是输入的文件名
void MD5File(CString *N, MD5Digest *Digest)
{
 HANDLE FileHandle;                       //文件读写句柄
 HANDLE MapHandle;                        //文件映射句柄
 MD5Context Context;                      //生成MD5的算法数据结构的上下文

 MD5Init(&Context);                       //MD5上下文初始化
 FileHandle = CreateFile(                 //打开文件,只读方式
  *N,
  GENERIC_READ,
  FILE_SHARE_READ | FILE_SHARE_WRITE,
  NULL,
  OPEN_EXISTING,
  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
  0);
 if(FileHandle!=INVALID_HANDLE_VALUE)     //如果打开文件成功
 {
 //创建文件映射
  MapHandle = CreateFileMapping(FileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
  if(MapHandle != NULL)                   //如果文件映射成功
  {
 // ULONGLONG 是系统预定义的64位无符号整数
 // FileSize: 文件大小
 // ptrFileMap: 文件指针,指示当前处理位置
 // BufferLength: 映射视图大小
   ULONGLONG FileSize, ptrFileMap, BufferLength;
 // ViewPointer :当前文件映射到内存的地址指针
   void *ViewPointer = NULL;
 // MemInfo:获取映射内存的信息
   MEMORY_BASIC_INFORMATION MemInfo;
 // 定义预备申请的映射内存大小,256MB
   DWORD ExpectantBufferLength = 0x10000000; 
   ptrFileMap = 0;
   BufferLength = 0;

   GetFileSizeEx(FileHandle, (LARGE_INTEGER *)&FileSize);
 // 主循环,完成整个文件的映射和处理
   do{ 
 // 如果预定的内存大小大于文件余下(还未处理的)尺寸,则取余下的大小为申请内存大小
    if( ExpectantBufferLength > (FileSize - ptrFileMap))
     ExpectantBufferLength = FileSize - ptrFileMap;
 // 映射内存过程,直到成功
    do{ 
 // 创建内存映射文件
     ViewPointer = MapViewOfFile(
      MapHandle,
      FILE_MAP_READ, 
      ptrFileMap >> 32,                                 // 映射到文件的起始位置,高32位
      ptrFileMap & 0xffffffff,                          // 映射到文件的起始位置,低32位
      ExpectantBufferLength);                           // 映射到的内存区间大小
     if(ViewPointer != NULL)                            // 如果映射成功
     {
      VirtualQuery(ViewPointer, &MemInfo, sizeof(MemInfo)); // 查询该块虚拟内存的信息
      BufferLength =  MemInfo.RegionSize ;              // 该虚拟内存的大小
 // 如果该块内存大小小于0,表示申请失败,我们将申请的大小减半,再次申请
      if(BufferLength <= 0) ExpectantBufferLength <<= 1;  
     }
 // 如果申请内存的大小大于1MB但还是不成功,则继续
    }while( BufferLength <= 0 && ExpectantBufferLength >= 0x100000 /* 1MB */);
  // 如果申请到的尺寸大于0,成功。开始处理文件。
    if(BufferLength > 0)      
    {
 // 以下BufferLength修正为传递到MD5计算过程内存的大小,
 // 不能超过文件末尾。这发生在文件最后映射的时候。
     if(ptrFileMap + BufferLength > FileSize)   
      BufferLength = FileSize - ptrFileMap;
     MD5Update(&Context, (BYTE *)ViewPointer, BufferLength);// MD5计算过程
     UnmapViewOfFile(ViewPointer);                       // 释放映射的内存
     ptrFileMap += BufferLength;                         // 递加文件指针为已经处理好的长度
    }                                                    //
   } while(ptrFileMap < FileSize);                       // 循环映射过程
   CloseHandle(MapHandle);                               // 关闭内存映射句柄
  }                                                      //
  CloseHandle(FileHandle);                               // 关闭文件句柄
 }
 MD5Final(&Context, Digest);                             // MD5上下文收尾计算过程
 return ;
}

 

Tags: , ,

发表评论

*