用Windows的文件映射机制实现大批量数据的快速存储

爱笑的窝心9

爱笑的窝心9

2016-02-19 12:59

生活已是百般艰难,为何不努力一点。下面图老师就给大家分享用Windows的文件映射机制实现大批量数据的快速存储,希望可以让热爱学习的朋友们体会到设计的小小的乐趣。

    上次做的 电子相册软件 ,为了解决大文件读取速度慢的问题,使用了Windows下的文件映射功能,使文件读取效率顿时得到了大幅度提升。(具体见: 一个读取速度超快的FileStream! )

    最近在做的一款软件,又使用到了这个函数,不过这次的要求是这样的:

    系统的主程序会持续的从网络端口上接收数据,这些数据需要变为实时的图像显示,但是同时图像显示部分又需要有回顾功能,也就是说能够任意将历史的数据调出来显示,为此就必须将所有历史数据保存下来,同时在需要的时候又能够快速从历史数据的指定位置将数据读出来。

    针对此,有两种方案:

    1)在主程序所在的机器接收数据前,使用另一台电脑,配合一个转发数据的程序,来达到数据的截取功能,由这个转发数据的程序将所有数据存储下来,并在主程序需要使用时,再用网络发送给主程序。

    2)使用主程序来进行数据存储,提高主程序存储数据的性能。

    不管采用何种方案,最终的瓶颈都将是大数据量的快速保存。由于整个系统内存使用和速度上的要求都很高,因此不可能将这样的数据放在程序内存里,也不可能使用普通的文件方式来记录数据。最终看来只有求助于Windows的文件映射功能了,它的优点是不会将要操作的文件读入内存,而是直接在系统层对文件进行读写,因此省去了文件的复制过程,特别是在读写大文件时,它能带来的速度提升是非常显著的。这样就可以将文件当作大内存来用了。

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

    新的程序写完,才发现原来以前用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);

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

/*!
从镜像文件的指定位置读取数据。
*/
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_SIZE32), (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)(mWriteMappingOffset32), (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)(mReadMappingOffset32), (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平台上了,不幸。

展开更多 50%)
分享

猜你喜欢

用Windows的文件映射机制实现大批量数据的快速存储

编程语言 网络编程
用Windows的文件映射机制实现大批量数据的快速存储

使用SqlBulkCopy进行数据大批量的迁移

编程语言 网络编程
使用SqlBulkCopy进行数据大批量的迁移

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

Oracle中大批量删除数据的方法

编程语言 网络编程
Oracle中大批量删除数据的方法

转移大批量Outlook邮件的秘决

电脑网络
转移大批量Outlook邮件的秘决

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

如何大批量激活Windows7系统与Office2010

电脑入门
如何大批量激活Windows7系统与Office2010

用Java实现HTML文件代替数据库存储数据

Web开发
用Java实现HTML文件代替数据库存储数据

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

Win8系统设置开启公用文件夹共享图文方法

Win8系统设置开启公用文件夹共享图文方法

一个简单的AJAX请求类

一个简单的AJAX请求类
下拉加载更多内容 ↓