一. 起因
年初,一位同学提取了如何实现倒叙输出文件,根据行号索引该行内容,我思考了一下,得出以下二种方案!
二. 方案
1)方案1:(此方案局限性较大,并且耗内存)
准备一个结构体,用来存储每行起始字符的地址以及该行字符的个数(带换行符),遍历此文件,将文件内容存放到堆中(耗内存的原因),在遍历过程中将每行的起始地址与该行大小分别存在准备好的结构体中!最后,倒叙读取结构体的内容,取得相应的行内容,写入文件,即可完成文件逆序输出,并且也可以根据行号索引来获取该行内容
2)方案2:(不耗内存,速率更快)
基本思想与方案一异曲同工,只不过用到文件映射(CreateFileMapping,MapViewOfFile),首先一次性将文件映射到本进程外的内存中,然后分块读取该共享内存的内容,此种方法及源码将在下片中详细介绍,这里就不做过多的叙述了!
三. 详细论述方案一的优缺点
1)优点
A. 只需一次遍历即可实现倒叙输出与按行号索引该行内容(只想到此点)
2)缺点
A. 由于在32为系统中,单进程我们能用到的地址空间仅有大约2G(进程中总地址空间4G, 除去空指针赋值区,64K禁入区,内核模式分区外,可用约2G),所以我们想要申请堆大小被限制在2G内,,加上进程中其他耗费的地址空间,故此方案局限性很大,仅能处理小文件(1G以内)!
B. 在编码过程中一次分配1G的内存空间是非常的不合理的!
C.效率低(一般情况下: 用户申请一段连续的大的内存块比将申请多段小的内存快耗时长)
如:申请一块60M的内存来处理数据比10次,每次处理10M来处理数据的速率低(方案2可以证实)
四.
是不是看到以上致命缺点对后面的内容没有兴趣了,其实我也是,由于不用注重速率了,所以我在此例中用到了新学的知识,oost::Shared_Ptr与Vector的结合使用,有兴趣的朋友可以看下,或者之处我使用之处的不足!
五. 实现方案代码详解
1)数据结构准备
typedef struct LINE_INFO{char *lpLineHead; INT32 LineSize; // contain '\n'}LINE_INFO;<span style="white-space:pre"> </span>
typedef struct LOAD_FILE_PARAM{WCHAR wcsFilePathName[MAX_PATH];<span style="white-space:pre"> </span> HWND hWnd;<span style="white-space:pre"> </span> INT32 MessageID;INT32 Param[4];}LOAD_FILE_PARAM;
<pre name="code" class="cpp">
char *m_lpFileData; vector<boost::shared_ptr<LINE_INFO>> m_LineInfoVector; LARGE_INTEGER m_liFileSize; HLog *m_lpHLog; WCHAR m_wcsReverseFilePath[MAX_PATH];
3)供用户使用的接口
LOAD_FILE_PARAM m_LoadFileParam;BOOL AnalysisFile(LOAD_FILE_PARAM *lpLoadFileParam); // asynBOOL CallBack_AnalysisFile(LOAD_FILE_PARAM *lpLoadFileParam); // synBOOL ReverseFileData(); // asynBOOL CallBack_ReverseFileData(); // synBOOL GetLineInfoByLineIndex(INT32 LineIndex, char csLineBuffer[], INT32 MaxBufferSize);UINT64 GetLineCount();
4)具体实现遍历文件代码
BOOL ReverseFile::CallBack_AnalysisFile(LOAD_FILE_PARAM *lpLoadFileParam)
{DWORD tBeginTime = GetTickCount();#pragma region Check Paramif (NULL == lpLoadFileParam || 0 == wcslen(lpLoadFileParam->wcsFilePathName)){m_lpHLog->TraceLog("Error(%s.%d): Param Illegal!\r\n", __FUNCTION__, __LINE__);return FALSE;}if (NULL == lpLoadFileParam->hWnd || lpLoadFileParam->MessageID < WM_USER){m_lpHLog->TraceLog("Waring(%s.%d): hWnd or MessageID Illegal!\r\n", __FUNCTION__, __LINE__);}WCHAR wcsPathName[MAX_PATH] = {};_snwprintf_s(wcsPathName, _TRUNCATE, lpLoadFileParam->wcsFilePathName);wcsrchr(wcsPathName, _T('\\'))[1] = _T('\0');_snwprintf_s(m_wcsReverseFilePath, _TRUNCATE, _T("%sReverse.log"), wcsPathName);
#pragma endregionHANDLE hFile = CreateFile(lpLoadFileParam->wcsFilePathName, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);if (INVALID_HANDLE_VALUE == hFile){m_lpHLog->TraceLog("Error(%s.%d): CreateFile Failed!\r\n", __FUNCTION__, __LINE__);return FALSE;}GetFileSizeEx(hFile, &m_liFileSize);if (m_liFileSize.QuadPart > MaxFileSize) // Over 1G{m_lpHLog->TraceLog("Error(%s.%d): File too Big!\r\n", __FUNCTION__, __LINE__);return FALSE;}m_lpFileData = new char[m_liFileSize.QuadPart]; // low?DWORD RelRead = 0;ReadFile(hFile, m_lpFileData, (DWORD)m_liFileSize.QuadPart, &RelRead, NULL); // low? char *lpHead = m_lpFileData;char *lpTail = m_lpFileData + m_liFileSize.QuadPart;while(lpHead < lpTail){char *lpErgodic = lpHead;while (*lpErgodic != 0x0A && lpErgodic < lpTail){lpErgodic++;}if (lpErgodic > lpTail && *lpErgodic != 0x0A){break;}if (*lpErgodic == 0x0A){lpErgodic++;}INT32 LineSize = lpErgodic - lpHead;boost::shared_ptr<LINE_INFO> lpLineInfo(new LINE_INFO());lpLineInfo->lpLineHead = lpHead;lpLineInfo->LineSize = LineSize;m_LineInfoVector.push_back(lpLineInfo);lpHead = lpErgodic;INT32 Progress = INT32(double(lpHead - m_lpFileData) * 100 / m_liFileSize.QuadPart);::PostMessageA(lpLoadFileParam->hWnd, lpLoadFileParam->MessageID, 0, (LPARAM)Progress); // Low}DWORD tEndTime = GetTickCount();m_lpHLog->TraceLog("Time is %d\r\n", tEndTime - tBeginTime);return TRUE;}
六.效果展示
(爱好刀塔的朋友(*^__^*) 嘻嘻)
七. 源码地址:http://download.csdn.net/detail/u012158162/9594174
注: 1)由于此源码只是一个范例,故中存在一些Bug,如没有考虑到重入问题,以及存在不合理的强转,都是可以修改及优化的地方!
2)如没有配置Boost库的朋友可以将用到Boost库智能指针的地方用常用指针替换,注意内存释放,防止泄漏!
3)如没有安装VLD的朋友,将头文件件中包含(#include “vld.h”)注释掉即可!