VC中海量文件读写类设计与应用(转)

VC中海量文件读写类设计与应用
 

沈瑞冰

摘要 本文阐述了海量文件读写的一般方法,并分析了该方法中存在的内存耗尽问题和解决办法,并就此设计了一个海量文件读写类,封装了海量文件读写操作,最后给出了一个应用实例。

关键词 VC,海量文件,封装


一、      引言

文件操作是应用程序最为基本的功能之一,Win32 APIMFC均提供有支持文件处理的函数和类,常用的有Win32 APICreateFile()WriteFile()ReadFile()MFC提供的CFile类等。一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储,再以通常的文件处理方法进行处理显然是行不通的。目前,对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的,但它仅以Win32方式,提供了一组函数,使用起来很不方便,尤其是该方法使用起来,容易出现内存耗尽问题,基于此,本文设计了一个海量文件操作的类,可以对海量文件完成与CFile类相似的功能。

二、海量文件读写的一般方法

首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。

三、存在问题和解决办法

利用内存映射文件实现海量文件,其机理实际上是应用了一种自己定义的内存映射机制,将硬盘上的数据地址通过某种特定的映射方式映射到某个虚拟的内存地址,整个硬盘数据地址都可以通过这种映射方式映射到某个虚拟内存地址区域,这样就可以像处理内存一样,处理硬盘数据。实际上,内存映射文件建立的初始阶段并没有为这些待处理的数据分配任何的内存,但是如果数据要得到处理,例如程序需要将某部分数据拷贝到指定的内存中进行处理,这个过程必然需要将数据从硬盘中读到与指定内存相独立的某个内存地址区域,然后才可能将这些数据从一块内存拷贝到另一块内存,在这个过程中,映射机制为处理的数据动态地分配了相应大小的内存。遗憾的是,不知道是出于何种考虑,映射机制没有动态释放这些动态分配的内存,也没有提供强制释放这些动态内存的函数,所以,随着程序遍历整个海量文件,动态分配的内存将与海量文件相当,出现内存耗尽现象。为了避免这种方法,笔者的方法是通过不断建立和撤消文件数据的映像,达到动态建立和释放内存的目的。

四、海量文件读写类设计

类一般包括两个部分:成员函数和成员变量,成员函数和成员变量又有公共、私有和保护之分。对于成员函数和成员变量,笔者浅显地认为,成员函数实际上是对一类对象的操作,而成员变量是与对象操作相适应的特性。一般公共函数和变量是类与其他类进行交流的外部接口,其他私有和保护成员,在没有继承的情况下,可以简单地认为是为了完成这些接口操作所有需要的内部变量和操作。对于文件读写操作而言,一般包括:文件打开,文件写,文件读,文件关闭,这些函数将作为类的公共成员函数,有时候外部类也需要获取文件特定偏移下的数据的虚拟地址,这个函数也作为公共成员函数。其他为实现外部操作,即公共成员函数的所有函数和变量都被设计为私有成员。由于对对象的同一种性质的操作,如文件读操作,可以有不同的方式,所以就需要对函数进行重载操作。在本类的设计中对文件打开,文件读,文件写都定义了多种重载操作。
    1.成员函数和变量定义
    将类取名为HugeFile,类的成员函数和成员变量定义如下:

class HugeFile
{
public:
 HugeFile();
 virtual ~HugeFile();
 BOOL HFileCreate(CString sFilePath, DWORD dFileLength);
 //文件打开,默认缓存大小
 BOOL HFileOpen(CString sFilePath);
 //文件打开,设置缓存大小
 BOOL HFileOpen(CString sFilePath , DWORD dCL);
 //改变缓存大小
 void HSetCacheLength(DWORD dCL);
 //文件读
 BOOL HFileRead( LPBYTE lpDest, DWORD dLength );
 void HFileClose();
 //文件读,指定起始地址
 BOOL HFileRead(LPBYTE lpDest, LPBYTE lpSource, DWORD dLength );
 //文件读,指定偏移量
 BOOL HFileRead(LPBYTE lpDest, DWORD dBytesOff, DWORD dLength);
 //根据偏移量返回数据虚拟内存地址
 LPVOID HSeekData( DWORD dBytesOff );
 //文件写
 BOOL HFileWrite( LPBYTE lpSource , DWORD dLength);
 //文件写,指定目的地址
 BOOL HFileWrite( LPBYTE lpSource , LPBYTE lpSource, DWORD dLength);
 //文件写,指定偏移量
 BOOL HFileWrite( LPBYTE lpSource , DWORD dBytesOff, DWORD dLength);

private: 
 BOOL HFileTry(DWORD dLength ); //试图动态释放内存
 //根据操作起始地址和操作长度更新参数
 void HRefreshWorkPara( LPBYTE lpStart, DWORD dLength);
 //根据操作长度更新参数
 void HRefreshWorkPara( DWORD dLength);
 //根据偏移量和长度更新参数
 void HRefreshWorkPara( DWORD dBytesOff, DWORD dLength);

private:
 HANDLE m_hFile;     //文件句柄
 HANDLE m_hFilemap;    //文件内存映射文件句柄
 LPVOID m_lpvFile;    //文件数据指针
 LPBYTE m_lpbWork;    //当前工作指向指针
 DWORD m_dPreBytesOff;   //记录读数据偏移
 DWORD m_dDataLength;   //文件打开后操作数据的长度
 DWORD m_dCacheLength;   //缓存大小

public:
 DWORD m_dFileSize;    //文件大小
 CString m_sFileName;   //文件名称
}

 

原文地址:https://www.cnblogs.com/mazhenyu/p/897452.html