| 沈瑞冰 摘要 本文闡述了海量文件讀寫的一般方法,并分析了該方法中存在的內存耗盡問題和解決辦法,并就此設計了一個海量文件讀寫類,封裝了海量文件讀寫操作,最后給出了一個應用實例。 關鍵詞 VC,海量文件,封裝 一、????? 引言 文件操作是應用程序最為基本的功能之一,Win32 API和MFC均提供有支持文件處理的函數和類,常用的有Win32 API的CreateFile()、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;???//文件名稱 } ? |