Windows對文件的讀寫提供了很豐富的操作手段,如:
1. FILE *fp, fstearm...; (C/C++)
2. CFile, CStdioFile...; (MFC)
3. CreateFile, ReadFile...;(API)
...
在處理一般的文件(文本/非文本),這些足夠了。然而在處理比較大的文件如
幾十M, 幾百M, 甚至上G的文件, 這時再用一般手段處理,系統就顯的力不從心了
要把文件讀出,再寫進,耗費的是CPU利用率與內存以及IO的頻繁操作。這顯然是
令用戶難以忍受的
為了解決這個吃內存,占CPU,以及IO瓶頸,windows核心編程提供了內存映射文件技術
(Maping File)
至於Maping File是什麼原理,我不多說了,網上轉載資源一籮筐,我只想從應用層
來考慮,怎樣用這個技術,實現日常項目中的應用
舉例來說:
可能項目中,會經常用到一些大量的常量,而這些大量常量用宏來替代寫再源文件中
顯然不可取,一般是寫在文件中,給常量一些編號,通過編號來索引
一般文件比較小時候,常用做法也是先預讀到內存中,畢竟從內存中讀比從文件中讀要快(IO操作的瓶頸)
比較好的做法,讀到STL MAP 中去:
例如一個索引文件:
SEU07201213=一顆欲枯的草
FANG=方
SEU07201214=CSDN
............
打開文件,解析=號,在解析方面有CString操作,strtok,strstr, boost 正則表達式匹配等等,但我比較喜歡
sscanf(szIndex, "%[^=]=%[^=]", sName, sValue);
sscanf(szIndex, "%[^=]=%s", sName, sValue);
fscanf(stream, "%[^=]=%[^=]", sName, sValue);
之類,
然後再定義一個map:
map<string, string> m_Map;
m_Map[sName] = sValue;
但是文件比較大的時候,筆者做過測試,用上面方法處理一個15M, 25萬行的文本文件,占用內存非常
的高,達70多M,處理的速度也非常的慢,這還不包括回寫到文件
這時,Maping File就派上用場了,這裡處理大文件就拋棄了map的應用(因為容器占用很多內存)
而是直接利用字符指針來操作,不用其他封裝,不多說了,請看示例:
#pragma warning(disable: 4786)
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
string GetValue(const TCHAR *, const TCHAR *); //根據name得value
void main(int argc, char* argv[])
{
// 創建文件對象(C: est.tsr)
HANDLE hFile = CreateFile("C:/test.tsr", GENERIC_READ | GENERIC_WRITE,0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("創建文件對象失敗,錯誤代碼:%d ", GetLastError());
return;
}
// 創建文件映射對象
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (hFileMap == NULL)
{
printf("創建文件映射對象失敗,錯誤代碼:%d ", GetLastError());
return;
}
// 得到系統分配粒度
SYSTEM_INFO SysInfo;
GetSystemInfo(&SysInfo);
DWORD dwGran = SysInfo.dwAllocationGranularity;
// 得到文件尺寸
DWORD dwFileSizeHigh;
__int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
qwFileSize |= (((__int64)dwFileSizeHigh) << 32);
// 關閉文件對象
CloseHandle(hFile);
// 偏移地址
__int64 qwFileOffset = 0;
// 塊大小
DWORD dwBlockBytes = 1000 * dwGran;
if (qwFileSize < 1000 * dwGran)
dwBlockBytes = (DWORD)qwFileSize;
if (qwFileOffset >= 0)
{
// 映射視圖
TCHAR *lpbMapAddress = (TCHAR *)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS,
0, 0,
dwBlockBytes);
if (lpbMapAddress == NULL)
...{
printf("映射文件映射失敗,錯誤代碼:%d ", GetLastError());
return;
}
//-----------------------訪問數據開始-------------------------
cout<<GetValue(lpbMapAddress,"SEU07201213")<<endl;
getchar();
//-----------------------訪問數據結束-------------------------
// 撤銷文件映像
UnmapViewOfFile(lpbMapAddress);
}
// 關閉文件映射對象句柄
CloseHandle(hFileMap);
}
string GetValue(const TCHAR *lpbMapAddress, const TCHAR *sName)
{
string sValue; // 存放 = 後面的value值
TCHAR *p1 = NULL, *p2 = NULL; // 字符指針
if((p1 = strstr(lpbMapAddress,sName)) != NULL) // 查找sName出現位置
{
if(p2 = strstr(p1,"/r/n")) *p2 = '/0'; // 查找"/r/n"(換行)出現位置
sValue = p1+strlen(sName)+strlen("="); // 指針移動"sName"+"="之後
*p2 = '/r'; // 還原*p2值,因為不還原會改變原文件結構
}
return sValue;
}
...
以上實現了根據索引name匹配value的簡單過程,經測試,同樣25W行文件,匹配耗費1秒不到,且不占本進程內存。
以上修改lpbMapAddress任意處值,也不需要重新回寫到文件,真正是大大提高了文件讀與寫的效率
本文出自 “情っ㈠顆欲枯旳草丶ゞ” 博客