程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 用C語言讀取大文件的問題--內存映射

用C語言讀取大文件的問題--內存映射

編輯:關於C語言

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任意處值,也不需要重新回寫到文件,真正是大大提高了文件讀與寫的效率
本文出自 “情っ㈠顆欲枯旳草丶ゞ” 博客

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved