最近,在处理大批量数据时,确实让自己纠结不小,其实这本身涉及到的问题在于时间与空间分配的问题。
如果追求时间,当然是把数据都存放到内存中。使用时,直接到内存对应位置存取,方便快捷。但如果数据庞大,比如一个数据分析报表,要查看一年的数据。可能我们可怜的2G内存电脑就吃不消了。当然如果有好的办法使数据压缩的话,那是最好的解决方案了。
我们经常查看到PDF这样的软件,打开那么大的文档,占用的内存却是那么的少,我们不禁会感到惊奇。现在,我们来钻研一下,如何来做到。
思路:
1.采用文件缓存的方式,先将我们的数据文件“格式化”写入到我们的临时文件中。
2.然后,根据请求的数据位置(Pos),格式化获取我们的数据。
3.最后安全关闭我们的文件,同时,删除临时文件。
使用该方法的关键就在于写入的数据必须是格式化写入的固定大小文件。也就是说,不能使用可变大小的类型作为格式化成员类型。比如说字符串类型string、CString等,只能采用WCHAR wchar_t[LENGTH]或CHAR char_t[LENGTH]。
为了提高文件操作效率,我们在创建临时文件时打开文件,当数据使用完之后,关闭文件。在读取数据过程中,采用文件映射的方式,可以有效的提高数据读速度。通过实验检测,读取256M文件到内存大概需要0.3s。但是写入256M内存到文件则需要3s时间。也就是说文件操作大部分时间耗费在写文件上。
由于,我们的数据是格式化的单条数据,如果一条条向文件中写,务必会耽误我们的操作时间。我们可以事先在内存中开辟一个32M的内存缓冲,然后,将数据先写入该缓冲区,当数据超过缓冲区大小,再一次写入文件中。
#pragma once /** * \class 数据交换缓存,用于内存数据与磁盘数据间的数据缓存操作 * \refer 该模块不能使用不能固定大小的模板T,特别是有未定义大小的string类型 */ #include "afx.h" static const int MAX_BUFFER_SIZE = 1024*1024*32; // 32兆 template<typename T> class ISwapBuffer{ public: ISwapBuffer() { m_hFile = INVALID_HANDLE_VALUE; m_hMapFile = INVALID_HANDLE_VALUE; m_pHead = NULL; } ~ISwapBuffer() { DeleteBuffer(); } public: /** * \brief 创建磁盘缓冲区 */ BOOL CreateBuffer() { if (INVALID_HANDLE_VALUE != m_hFile) { return FALSE; } BOOL bRet = GetUniqueTempName(m_fileName); if (FALSE == bRet) { return FALSE; } m_hFile = ::CreateFile(m_fileName,GENERIC_READ | GENERIC_WRITE,0, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (INVALID_HANDLE_VALUE == m_hFile) { return FALSE; } m_sizeT = 0; // 记录模板条数 m_iWriteSize = 0; // 写文件大小 m_lengthT = sizeof(T); // 单条模板长度 m_pBTmpBuffer = NULL; m_pBTmpBuffer = new BYTE[MAX_BUFFER_SIZE]; // 创建内存临时缓冲区 return TRUE; } /** * \brief 写临时缓冲区文件 * \param[in] buffer 内存数据缓存指针 * \param[in] size 写入缓冲区大小 */ int WriteBuffer(const T &buffer) { if (m_pBTmpBuffer == NULL) { return -1; } ::CopyMemory(m_pBTmpBuffer+m_iWriteSize,&buffer,m_lengthT); m_iWriteSize += m_lengthT; if (m_iWriteSize+m_lengthT > MAX_BUFFER_SIZE) { if (INVALID_HANDLE_VALUE == m_hFile) { return -1; } ::SetFilePointer(m_hFile,NULL,NULL,FILE_END); DWORD dw = 0; BOOL bRet = ::WriteFile(m_hFile,m_pBTmpBuffer,m_iWriteSize,&dw,NULL); if ((false == bRet) && (dw != m_iWriteSize)) { return -1; } ::FlushFileBuffers(m_hFile); m_iWriteSize = 0; return dw; } return 1; } /** * \brief 读临时缓冲区文件 * \param[out] buffer 内存分配缓冲区指针 * \param[in] size 读取缓冲区大小 */ BOOL ReadBuffer(T &buffer) { DeleteTmpBuffer(); if (INVALID_HANDLE_VALUE == m_hFile) { return FALSE; } if (m_iWriteSize > 0) { ::SetFilePointer(m_hFile,NULL,NULL,FILE_END); DWORD dw = 0; BOOL bRet = ::WriteFile(m_hFile,m_pBTmpBuffer,m_iWriteSize,&dw,NULL); if ((false == bRet) && (dw != m_iWriteSize)) { return -1; } ::FlushFileBuffers(m_hFile); } GetFileSize(); SetFilePointer(m_hFile,NULL,NULL,FILE_BEGIN); m_hMapFile = CreateFileMapping(m_hFile,NULL,PAGE_READONLY,0,0,NULL); if (INVALID_HANDLE_VALUE == m_hMapFile) { return FALSE; } m_pHead = (PBYTE)MapViewOfFile(m_hMapFile,FILE_MAP_READ,0,0,0); m_nReadCnt = 0; return ReadNextBuffer(buffer); } /** * \brief 读下一条临时文件缓冲区 * \param[out] buffer 内存分配缓冲区指针 * \param[in] size 读取缓冲区大小 */ BOOL ReadNextBuffer(T &buffer) { if (m_nReadCnt >= m_sizeT) { return FALSE; } buffer = *(T*)(m_pHead+m_nReadCnt*m_lengthT); m_nReadCnt ++; return TRUE; } /** * \brief 从指定位置读取 * \param[out] buffer 内存分配缓冲区指针 * \param[in] nItem 指定项位置 * \param[in] tFormatSize 格式化结构大小 * \param[in] size 获取数据缓冲区大小(默认为格式化结构大小) */ BOOL ReadAt(T &buffer,int nItem) { if (INVALID_HANDLE_VALUE == m_hFile) { return FALSE; } if (NULL == m_pHead) { ::SetFilePointer(m_hFile,NULL,NULL,FILE_END); DWORD dw = 0; BOOL bRet = ::WriteFile(m_hFile,m_pBTmpBuffer,m_iWriteSize,&dw,NULL); if ((false == bRet) && (dw != m_iWriteSize)) { return -1; } ::FlushFileBuffers(m_hFile); GetFileSize(); SetFilePointer(m_hFile,NULL,NULL,FILE_BEGIN); m_hMapFile = CreateFileMapping(m_hFile,NULL,PAGE_READONLY,0,0,NULL); if (INVALID_HANDLE_VALUE == m_hMapFile) { return FALSE; } m_pHead = (PBYTE)MapViewOfFile(m_hMapFile,FILE_MAP_READ,0,0,0); DeleteTmpBuffer(); } if (nItem > m_sizeT || nItem <= 0) { return FALSE; } if (NULL == m_pHead) { DWORD dw = GetLastError(); TRACE(L"%d",dw); }else { buffer = *(T*)(m_pHead + (nItem-1)*m_lengthT); } return TRUE; } /** * \brief 删除磁盘临时文件缓冲区 */ void DeleteBuffer() { if (INVALID_HANDLE_VALUE != m_hMapFile) { UnmapViewOfFile(m_pHead); CloseHandle(m_hMapFile); m_pHead = NULL; m_hMapFile = INVALID_HANDLE_VALUE; } if (INVALID_HANDLE_VALUE != m_hFile) { CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; } DeleteTmpBuffer(); DeleteFile(m_fileName); } BOOL GetFileSize() { DWORD end = SetFilePointer(m_hFile,0,0,FILE_END); DWORD beg = SetFilePointer(m_hFile,0,0,FILE_BEGIN); m_sizeT = (end - beg)/m_lengthT; return TRUE; } protected: /** * \brief 获取唯一临时文件名 */ BOOL GetUniqueTempName(CString &fileName) { fileName.Empty(); //Get the temporary files directory. TCHAR szTempPath[MAX_PATH]; DWORD dwResult = ::GetTempPath(MAX_PATH, szTempPath); if (dwResult==0) return false; //Create a unique temporary file. TCHAR szTempFile[MAX_PATH]; UINT nResult=GetTempFileName (szTempPath,_T("~tmp"),0,szTempFile); if (dwResult==0) return false; fileName=szTempFile; return true; } void DeleteTmpBuffer() { if (NULL != m_pBTmpBuffer) { delete []m_pBTmpBuffer; m_pBTmpBuffer = NULL; } } private: /** * \brief 文件句柄 */ HANDLE m_hFile; /** * \brief 映射文件句柄 */ HANDLE m_hMapFile; /** * \brief 映射内存头 */ BYTE *m_pHead; /** * \brief 临时内存缓冲区 */ BYTE *m_pBTmpBuffer; /** * \brief 文件名 */ CString m_fileName; /** * \brief 模板大小 */ int m_lengthT; /** * \brief 文件记录模板数大小 */ int m_sizeT; /** * \brief 写计数 */ int m_iWriteSize; /** * \brief 读取模板计数 */ int m_nReadCnt; };
以上为随便写的一个测试代码,其中有很多bugs,还望同行们给予指出。本代码已经经过测试,1000000条762字节的数据,测试效果还行。