學習筆記:內存映射處理大文件

今天工作中遇到一個處理大文件的問題,開始以爲是MapViewOfFile在讀取文件時出了問題,所以稍微研究了下內存映射的問題。

最後分析雖然不是這方面的問題,但還是有點收穫的。(網上搜集的資料整理)

http://www.yesky.com/405/1756405.shtml

http://blog.csdn.net/dsg333/article/details/8260178

/////////////////////////////////////////

1、爲何文件映射適合處理大文件?

文件操作是應用程序最爲基本的功能之一,Win32 API和MFC均提供有支持文件處理的函數和類,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile類等。一般來說,以上這些函數可以滿足大多數 場合的要求,但是對於某些特殊應用領域所需要的動輒幾十GB、幾百GB、乃至幾TB的海量存儲,再以通常的文件處理方法進行處理顯然是行不通的。目前,對 於上述這種大文件的操作一般是以內存映射文件的方式來加以處理的。

內存文件映射是Windows的一種內存管理方法,提供了一個統一的內存管理特徵,使應用程序可以通過內存指針對磁盤上的文件進行訪問,其過程就如同對 加載了文件的內存的訪問。通過文件映射這種使磁盤文件的全部或部分內容與進程虛擬地址空間的某個區域建立映射關聯的能力,可以直接對被映射的文件進行訪 問,而不必執行文件I/O操作也無需對文件內容進行緩衝處理。 內存文件映射的這種特性是非常適合於用來管理大尺寸文件的。

2、內存映射原理

網上這篇文章講得很清晰:http://blog.csdn.net/mg0832058/article/details/5890688

3、MapViewOfFile參數說明

MapViewOfFile(
__in HANDLE hFileMappingObject,              //共享文件對象
__in DWORD dwDesiredAccess,                 //文件共享屬性
__in DWORD dwFileOffsetHigh,                   //文件共享區的偏移地址高32位
__in DWORD dwFileOffsetLow,                    //文件共享區的偏移地址低32位
__in SIZE_T dwNumberOfBytesToMap       //共享數據長度
);

內存文件映函數的第一個參數爲CreateFileMapping()所返回的內存映射文件對象句柄,第二個參數指定了對文件映像的訪問類型,可能取值有 FILE_MAP_WRITE、FILE_MAP_READ、FILE_MAP_ALL_ACCESS和FILE_MAP_COPY等幾種,具體的設置要 根據文件映射對象允許的保護模式而定。根據前面代碼的設置,這裏應該使用FILE_MAP_ALL_ACCESS參數。這種機制爲對象的創建者提供了對映 射此對象的方式進行控制的能力。

接下來的2個參數分別指定了內存映射文件的64位偏移地址的低32位和高32位地址,該地址是從內存映射文件頭位置到映像開始位置的距離。最後的參數指定了視圖的大小,如果設置爲0,前面的偏移地址將被忽略,系統將會把整個文件映射爲一個映像。

MapViewOfFile()如果成功執行,將返回一個指向文件映像在進程的地址空間中的起始地址的指針。如果失敗,則返回NULL。在進程中,可以爲 同一個文件映射對象創建多個文件映像,這些映像可以在系統中共存和重疊,也可以與對應的文件映射對象大小不相一致,但不能大於文件映射對象的大小。

4、VirtualProtect()函數說明

BOOL WINAPI VirtualProtect(
  __in   LPVOID lpAddress,
  __in   SIZE_T dwSize,
  __in   DWORD flNewProtect,
  __out  PDWORD lpflOldProtect
);
我們在調用MapViewOfFile之後,根據需求可以調用該函數,此函數可以改變調用進程虛擬地址空間中已提交頁上的一段內存的保護屬性。
第 1 個參數 lpAddress 是輸入參數,是虛擬內存基地址。
第 2 個參數 dwSize 是輸入參數,表示要改變內存區域的大小。
第 3 個參數 flNewProtect 是輸入參數,給出了要設置的新的保護屬性,可以爲 PAGE_READONLY, PAGE_EXECUTE,PAGE_EXECUTE_READ 等。
第 4 個參數 lpflOldProtect 是輸出參數,指向保存原保護屬性值(DWORD),當其爲 NULL 或 指向一個錯誤的變量時函數都將失敗。

5、文件分塊處理

文件不太大的話,調用MapViewOfFile參數可按默認處理:

lpFileBase = (LPBYTE)MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
if(NULL == lpFileBase)
{
	CloseHandle(hFileMapping);
	CloseHandle(hFile);
	return;
}

文件比較大,需分塊處理時,代碼如下:

// 創建文件對象
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); 

代碼的意思是一次每次映射dwBlockBytes個字節,即1000*64KB(只有在最後次映射不夠這麼多時才映射 qwFileSize - T_newmap個字節),雖說每次映射1000*64KB字節,但當讀取超過900*64KB個字節時,就進行下一次映射, 下一次映射就從上次的900*64KB處開始,映射1000*64KB。所以每次的偏移量就是900*64KB的倍數,也就是用t_newmap來偏移。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章