C++使用內存映射讀寫大文件

由於公司項目要求,組件A每毫秒會產生五百萬條數據,需要進行存儲,使用IO流速度過於緩慢,於是決定採用內存映射的方法進行存儲,效率提高了許多,於是對查詢的資料,以及寫代碼過程中遇到的一些問題進行整理。

對於內存映射,需要用到幾個關鍵的Windows API:

HANDLE CreateFile(LPCTSTR lpFileName,
                    DWORD dwDesiredAccess,
                    DWORD dwShareMode,
                    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                    DWORD dwCreationDisposition,
                    DWORD dwFlagsAndAttributes,
                    HANDLE hTemplateFile);

函數CreateFile()用來創建/打開一個文件內核對象,並將其句柄返回,在調用該函數時需要根據是否需要數據讀寫和文件的共享方式來設置參數dwDesiredAccess和dwShareMode,錯誤的參數設置將會導致相應操作時的失敗。

具體用法可以參見最後的代碼。

第二個:

HANDLE CreateFileMapping(HANDLE hFile,
                           LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
                           DWORD flProtect,
                           DWORD dwMaximumSizeHigh,
                           DWORD dwMaximumSizeLow,
                           LPCTSTR lpName);

創建一個文件映射內核對象,通過參數hFile指定待映射到進程地址空間的文件句柄(該句柄由CreateFile()函數的返回值獲取)。由於內存映射文件的物理存儲器實際是存儲於磁盤上的一個文件,而不是從系統的頁文件中分配的內存,所以系統不會主動爲其保留地址空間區域,也不會自動將文件的存儲空間映射到該區域,爲了讓系統能夠確定對頁面採取何種保護屬性,需要通過參數flProtect來設定,保護屬性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分別表示文件映射對象被映射後,可以讀取、讀寫文件數據。在使用PAGE_READONLY時,必須確保CreateFile()採用的是GENERIC_READ參數;PAGE_READWRITE則要求CreateFile()採用的是GENERIC_READ|GENERIC_WRITE參數;至於屬性PAGE_WRITECOPY則只需要確保CreateFile()採用了GENERIC_READ和GENERIC_WRITE其中之一即可。DWORD型的參數dwMaximumSizeHigh和dwMaximumSizeLow也是相當重要的,指定了文件的最大字節數,由於這兩個參數共64位,因此所支持的最大文件長度爲16EB,幾乎可以滿足任何大數據量文件處理場合的要求。

第三個:

LPVOID MapViewOfFile(HANDLE hFileMappingObject,
                        DWORD dwDesiredAccess,
                        DWORD dwFileOffsetHigh,
                        DWORD dwFileOffsetLow,
                        DWORD dwNumberOfBytesToMap);

MapViewOfFile()函數負責把文件數據映射到進程的地址空間,參數hFileMappingObject爲CreateFileMapping()返回的文件映像對象句柄。參數dwDesiredAccess則再次指定了對文件數據的訪問方式,而且同樣要與CreateFileMapping()函數所設置的保護屬性相匹配。雖然這裏一再對保護屬性進行重複設置看似多餘,但卻可以使應用程序能更多的對數據的保護屬性實行有效控制。MapViewOfFile()函數允許全部或部分映射文件,在映射時,需要指定數據文件的偏移地址以及待映射的長度。其中,文件的偏移地址由DWORD型的參數dwFileOffsetHigh和dwFileOffsetLow組成的64位值來指定,而且必須是操作系統的分配粒度的整數倍,對於Windows操作系統,分配粒度固定爲64KB。當然,也可以通過如下代碼來動態獲取當前操作系統的分配粒度:

SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;

在完成對映射到進程地址空間區域的文件處理後,需要通過函數UnmapViewOfFile()完成對文件數據映像的釋放以及文件的關閉:
 

BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);

唯一的參數lpBaseAddress指定了返回區域的基地址,必須將其設定爲MapViewOfFile()的返回值。在使用了函數MapViewOfFile()之後,必須要有對應的UnmapViewOfFile()調用,否則在進程終止之前,保留的區域將無法釋放。除此之外,前面還曾由CreateFile()和CreateFileMapping()函數創建過文件內核對象和文件映射內核對象,在進程終止之前有必要通過CloseHandle()將其釋放,否則將會出現資源泄漏的問題。

具體實現如下:

#include <iostream>
#include <atlstr.h> 
#include <Windows.h>
#include <WinBase.h>
using namespace std;
 
int main()
{
    const char* shared_name = "test";
    const char* file_name = "C:\\1.txt";
    const DWORD mmf_size = 512*1024;
    //存取模式
    DWORD access_mode = (GENERIC_READ|GENERIC_WRITE);
    //共享模式
    DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
    //文件屬性
    DWORD flags = FILE_FLAG_SEQUENTIAL_SCAN;//|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_NO_BUFFERING;
    DWORD error_code;
     
    //格式轉換
	//const char * 轉 LPCWSTR
    char* szStr = "Mikasoi";  
	CString str = CString(file_name);
	USES_CONVERSION;
	LPCWSTR wszClassName = A2CW(W2A(str));
	str.ReleaseBuffer();
	
    //創建文件
    HANDLE mmHandle =
        CreateFile(wszClassName,
             access_mode, 
             share_mode, 
             NULL, 
             OPEN_ALWAYS,
             flags,
             NULL);
 
    if (mmHandle == INVALID_HANDLE_VALUE) 
	{
        error_code = GetLastError();
        cout<<"創建mmf失敗:"<<error_code<<endl;
        return -1; 
    }
    
    DWORD high_size;
    DWORD file_size = GetFileSize(mmHandle, &high_size);
    if (file_size == 0xFFFFFFFF && (error_code = GetLastError()) != 0) 
	{
        CloseHandle(mmHandle);            
        cout<<"error:"<<error_code<<endl;
        return -2;
    }
    cout<<"create mmf sucessfully"<<endl;

    DWORD size_high = 0;
    //創建文件映射,如果要創建內存頁面文件的映射,第一個參數設置爲INVALID_HANDLE_VALUE
    HANDLE mmfm = CreateFileMapping(mmHandle,
        NULL,
        PAGE_READWRITE,
        size_high, 
        mmf_size, 
        shared_name);

    error_code = GetLastError();
    if(0 != error_code)
	{
        cout<<"createFileMapping error"<<error_code<<endl;
        return -3;
    }
    if(mmfm == NULL){
        if(mmHandle != INVALID_HANDLE_VALUE){
            CloseHandle(mmHandle);
        }
    }

    size_t view_size = 1024*256;
    DWORD view_access = FILE_MAP_ALL_ACCESS;

    //獲得映射視圖
    char* mmfm_base_address = (char*)MapViewOfFile(mmfm,view_access,0,0,view_size);
    if(mmfm_base_address == NULL)
	{
        error_code = GetLastError();
        if(error_code != 0)
		{
            cout<<"error code "<<error_code<<endl;
            return -4;
        }
    }
	else
	{
        char write_chars[] = "hello chars";
        const size_t write_chars_size = sizeof(write_chars);
        
        //向內存映射視圖中寫數據
        memcpy(mmfm_base_address,write_chars,write_chars_size);
         
        size_t position = 0;
        char read_chars[write_chars_size];

        //讀數據
        memcpy(read_chars,mmfm_base_address,write_chars_size);
        cout<<"read chars "<<read_chars<<endl;
         
        //卸載映射
        UnmapViewOfFile(mmfm_base_address);
        //關閉內存映射文件
        CloseHandle(mmfm);
        //關閉文件
        CloseHandle(mmHandle);
    } 
 
    system("pause");

    return EXIT_SUCCESS;
}

這個代碼應該是沒有問題的,不過是在公司寫好的,在家裏寫的博客,可能記憶有些偏差,不過應該也不會出現編譯錯誤。

 

附:參考文章:

https://blog.csdn.net/mikasoi/article/details/81347854

https://www.cnblogs.com/yukaizhao/archive/2011/05/18/MapViewOfFile_CreateFileMapping.html

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