程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> 用Windows的文件映射機制,實現大批量數據的快速存儲

用Windows的文件映射機制,實現大批量數據的快速存儲

編輯:vc教程

    上次做的 電子相冊軟件 ,為了解決大文件讀取速度慢的問題,使用了Windows下的文件映射功能,使文件讀取效率頓時得到了大幅度提升。(具體見: 一個讀取速度超快的FileStream! )

    最近在做的一款軟件,又使用到了這個函數,不過這次的要求是這樣的:

    系統的主程序會持續的從網絡端口上接收數據,這些數據需要變為實時的圖像顯示,但是同時圖像顯示部分又需要有回顧功能,也就是說能夠任意將歷史的數據調出來顯示,為此就必須將所有歷史數據保存下來,同時在需要的時候又能夠快速從歷史數據的指定位置將數據讀出來。

    針對此,有兩種方案:

    1)在主程序所在的機器接收數據前,使用另一台電腦,配合一個轉發數據的程序,來達到數據的截取功能,由這個轉發數據的程序將所有數據存儲下來,並在主程序需要使用時,再用網絡發送給主程序。

    2)使用主程序來進行數據存儲,提高主程序存儲數據的性能。

    不管采用何種方案,最終的瓶頸都將是大數據量的快速保存。由於整個系統內存使用和速度上的要求都很高,因此不可能將這樣的數據放在程序內存裡,也不可能使用普通的文件方式來記錄數據。最終看來只有求助於Windows的文件映射功能了,它的優點是不會將要操作的文件讀入內存,而是直接在系統層對文件進行讀寫,因此省去了文件的復制過程,特別是在讀寫大文件時,它能帶來的速度提升是非常顯著的。這樣就可以將文件當作大內存來用了。

    新的程序寫完,才發現原來以前用Delphi實現的那個版本根本不能夠訪問大文件,只是在讀取文件速度上有些優勢而已,因為我過去的做法是在CreateFileMapping()之後,將整個文件的內存都MapViewOfFile()到程序內存裡,這樣文件一大,程序仍然無法打開文件。現在才知道,MapViewOfFile()函數是可以指定從文件的哪個部分進行Map,同時Map多少進入內存的。對於真正的大文件(幾個G的)的訪問,因該是一塊一塊的Map,然後進行讀寫。同時Map的起始地址和Map的內存大小都必須是64K的整數倍,不然MapVIEwOfFile()函數會失敗。

    最初的一個簡單的程序版本花了1個小時不到就寫完了,但是一測試卻發現有個嚴重的問題:沒有考慮數據在Map內存的邊界上時的問題。當要讀寫的數據正好在Map的內存的邊界上時,從當前Map的內存中就只能取到數據的前半部分,另一部分的數據必須Map文件的下一塊地址才可能取到。因此對程序又進行了徹底的改造,讓其能夠在讀取一個Map內存發現不夠時,自動打開下一個Map內存。

    總算大功告成,寫了一個簡單的測試程序對其進行測試,速度和正確性都都非常理想。 最後,貼上程序代碼:

#ifndef enstfilecache_h

#define enstfilecache_h

#include <QtCore/QVariant>
#include <QtCore/QObject>

#include <Windows.h>

#include "../enstdefine.h"

/*! rIEf sampler::enstFileCache
author tony (http://www.tonixsoft.com)
version 0.08
date 2006.05.10  

   基於文件鏡像的快速大容量數據存儲類。該類使用Windows下的 CreateFileMapping() 函數實現,不支持其它系統。
該類中的文件鏡像原理可以參考:http://www.juntuan.Net/hkbc/winbc/n/2006-04-19/14320.Html
當要讀取或寫入的數據跨多個MapView的時候,該類會自動處理MapVIEw的切換。
*/
class enstFileCache : public QObject
{
Q_OBJECT

public:
/*!
construct the class.
*/
enstFileCache();

/*!
destruct the class.
*/
~enstFileCache();

/*!
打開鏡像文件。
@return 當打開鏡像文件失敗時返回false,比如磁盤空間不夠。
*/
bool CreateFileCache(const QString &pFileName);

/*!
向鏡像文件中追加數據。
*/
bool AppendData(T_INT8* pData, int pDataLength);

/*!
從鏡像文件的指定位置讀取數據。
*/
bool ReadData(T_INT64 pAddressOffset, T_INT8* pData, int pDataLength);

protected:
void DumpWindowsErrorMessage();

private:
T_INT64 mMappingVIEwSize;

HANDLE mFileHandle;

HANDLE mMappingHandle;

T_INT64 mWriteMappingOffset;

LPVOID mWriteBuffer;

T_INT64 mWriteBufferOffset;

T_INT64 mReadMappingOffset;

LPVOID mReadBuffer;
};

#endif //enstfilecache_h

=====================================================

#include "enstfilecache.h"

#include "../enstsvcpack.h"

//#define FILE_CACHE_SIZE 0x40000000 /* = 1GB */
#define FILE_CACHE_SIZE 0x01E00000 /* = 30MB */

enstFileCache::enstFileCache()
{
mMappingVIEwSize = 1024*(64*1024); //window's default block size is 64KB

mFileHandle = INVALID_HANDLE_VALUE;
mMappingHandle = NULL;
mWriteMappingOffset = 0;
mWriteBuffer = NULL;
mWriteBufferOffset = 0;
mReadMappingOffset = 0;
mReadBuffer = NULL;
}

enstFileCache::~enstFileCache()
{
if (mWriteBuffer != NULL) {
UnmapVIEwOfFile(mWriteBuffer);
}
if (mReadBuffer != NULL) {
UnmapVIEwOfFile(mReadBuffer);
}
if (mMappingHandle != NULL) {
CloseHandle(mMappingHandle);
}
if (mFileHandle != INVALID_HANDLE_VALUE) {
CloseHandle(mFileHandle);
}
}

bool enstFileCache::CreateFileCache(const QString &pFileName)
{
enstLogService *logservice = enstLogService::GetMyAddr();

mFileHandle = CreateFile(pFileName.toAscii(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (mFileHandle == INVALID_HANDLE_VALUE) {
DumpWindowsErrorMessage();
logservice->AppendLog(this, "Error when create file.");
return false;
}
mMappingHandle = CreateFileMapping(mFileHandle, NULL, PAGE_READWRITE, (DWORD)(FILE_CACHE_SIZE>>32), (DWord)(FILE_CACHE_SIZE & 0xFFFFFFFF), NULL);
if (mMappingHandle == NULL) {
if (GetLastError() == 112) {
logservice->AppendLog(this, "Looks like it's not enough space on the disk.");
}
DumpWindowsErrorMessage();
logservice->AppendLog(this, "Error when create file mapping.");
return false;
}
mWriteMappingOffset = 0;
mWriteBuffer = NULL;
mWriteBufferOffset = 0;
mReadMappingOffset = 0;
mReadBuffer = NULL;
return true;
}

bool enstFileCache::AppendData(T_INT8* pData, int pDataLength)
{
int datawrote = 0;
do {
int datacanwrite = mMappingVIEwSize - mWriteBufferOffset;
if (mWriteBuffer && datacanwrite <= 0) {
UnmapVIEwOfFile(mWriteBuffer);
mWriteBuffer = NULL;
mWriteMappingOffset += mMappingVIEwSize;
}

if (mWriteBuffer == NULL) {
mWriteBuffer = MapVIEwOfFile(mMappingHandle, FILE_MAP_WRITE, (DWORD)(mWriteMappingOffset>>32), (DWord)(mWriteMappingOffset & 0xFFFFFFFF), mMappingVIEwSize);
mWriteBufferOffset = 0;
datacanwrite = mMappingVIEwSize - mWriteBufferOffset;
if (! mWriteBuffer) {
DumpWindowsErrorMessage();
enstLogService *logservice = enstLogService::GetMyAddr();
logservice->AppendLog(this, "Error when map vIEw of file.");
return false;
}
}
int datatowrite = pDataLength - datawrote;
int actualdatatowrite = (datacanwrite >= datatowrite)?datatowrite:datacanwrite;
memcpy((PBYTE)mWriteBuffer+mWriteBufferOffset, (PBYTE)pData+datawrote, actualdatatowrite);
mWriteBufferOffset += actualdatatowrite;
datawrote += actualdatatowrite;
} while (datawrote < pDataLength);
return true;
}

bool enstFileCache::ReadData(T_INT64 pAddressOffset, T_INT8* pData, int pDataLength)
{
int datareaded = 0;
do {
int datacanread = mReadMappingOffset + mMappingVIEwSize - pAddressOffset - datareaded;
if (mReadBuffer && (datacanread <= 0 || datacanread > mMappingVIEwSize)) {
UnmapVIEwOfFile(mReadBuffer);
mReadBuffer = NULL;
}
if (mReadBuffer == NULL) {
mReadMappingOffset = (pAddressOffset + datareaded) / mMappingViewSize * mMappingVIEwSize;
mReadBuffer = MapVIEwOfFile(mMappingHandle, FILE_MAP_READ, (DWORD)(mReadMappingOffset>>32), (DWord)(mReadMappingOffset & 0xFFFFFFFF), mMappingVIEwSize);
datacanread = mReadMappingOffset + mMappingVIEwSize - pAddressOffset - datareaded;
if (! mReadBuffer) {
DumpWindowsErrorMessage();
enstLogService *logservice = enstLogService::GetMyAddr();
logservice->AppendLog(this, "Error when map vIEw of file.");
return false;
}
}
int datatoread = pDataLength - datareaded;
int actualdatatoread = (datacanread >= datatoread)?datatoread:datacanread;
memcpy((PBYTE)pData+datareaded, (PBYTE)mReadBuffer+pAddressOffset-mReadMappingOffset+datareaded, actualdatatoread);
datareaded += actualdatatoread;
} while (datareaded < pDataLength);
return true;
}

void enstFileCache::DumpWindowsErrorMessage()
{
LPVOID lpMsgBuf;
DWord dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
enstLogService *logservice = enstLogService::GetMyAddr();
logservice->AppendLog(this, (LPTSTR)lpMsgBuf);
//puts((LPTSTR)lpMsgBuf);

LocalFree(lpMsgBuf);
}

#ifdef WIN32
#include "moc_enstfilecache.cpp"
#endif

由於系統是用QT開發的,因此類中使用了不少QT的類,同時也使用了系統中的部分工具類,不過基本工作原理相信你是能夠看懂的。另外,整個系統是計劃要跨平台使用的,因此今後還需要實現Linux下的類似功能,目前這個版本被綁定在Windows平台上了,不幸。

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