大文件数据操作
我们以全国开放数据(数据纯属伪造)为例,它的文件格式是csv,也是经常使用的一种文件存储格式,它是以逗号和换行来进行区分数据段和行来进行数据的区分。
今天我们把一个文件中的2000000条数据进行处理。
文件操作的几种方式
#include <stdio.h>
#include <Windows.h>
#include <clocale>
const UINT MAXLINESIZE = 1024;
wchar_t *Utf8ToUnicode(BYTE *strUtfData)
{
int iLen = MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<char *>(strUtfData), -1, nullptr, 0);
wchar_t *strRet = new wchar_t[iLen];
MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<char *>(strUtfData), -1, strRet, iLen);
return strRet;
}
BOOL ReadLine(const HANDLE hFile, BYTE *strBuf, UINT uMax = MAXLINESIZE)
{
BOOL bRet = FALSE;
BYTE byteTemp;
UINT uIndex = 0;
DWORD dwReadLen;
while (ReadFile(hFile, &byteTemp, sizeof(BYTE), &dwReadLen, nullptr))
{
if (dwReadLen != sizeof(BYTE))
{
break;
}
if (byteTemp == '\n')
{
bRet = TRUE;
break;
}
else
{
if (uIndex < uMax)
{
strBuf[uIndex++] = byteTemp;
}
}
}
return bRet;
}
void ReadFromFile()
{
setlocale(LC_ALL, "chs");
wchar_t *wstrFilePath = L"1.csv";
HANDLE hFile = CreateFile(wstrFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (INVALID_HANDLE_VALUE != hFile)
{
DWORD dwPrevTime = GetTickCount();
DWORD dwCount = 0;
BYTE byteArray[MAXLINESIZE] = { 0 };
//wchar_t *wstrSearchData = L"1990";
while (ReadLine(hFile, byteArray))
{
wchar_t *wstrTemp = Utf8ToUnicode(byteArray);
if (wcsstr(wstrTemp, L"1990") != nullptr)
{
++dwCount;
}
memset(byteArray, 0, MAXLINESIZE);
}
DWORD dwCurTime = GetTickCount();
DWORD dwTimeElaped = dwCurTime - dwPrevTime;
wprintf(L"共找到:%d 条符合条件的记录, 用时:%d毫秒。\r\n", dwCount, dwTimeElaped);
CloseHandle(hFile);
}
else
{
// 错误处理
wchar_t wstrMessage[MAXBYTE] = { 0 };
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, wstrMessage, MAXBYTE, nullptr);
wprintf(L"打开文件失败,错误原因是:%s\r\n", wstrMessage);
}
}
void ReadFromFileMap()
{
setlocale(LC_ALL, "chs");
wchar_t *wstrFilePath = L"1.csv";
DWORD dwPrevTime = GetTickCount();
DWORD dwCount = 0;
HANDLE hFile = CreateFile(wstrFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (INVALID_HANDLE_VALUE != hFile)
{
HANDLE hFileMap = CreateFileMapping(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (hFileMap != nullptr)
{
char *pFileMapAddr = (char *)MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
MEMORY_BASIC_INFORMATION memBuffer;
VirtualQuery(pFileMapAddr, &memBuffer, sizeof(MEMORY_BASIC_INFORMATION));
BYTE byteTemp[MAXLINESIZE] = { 0 };
UINT uIndex = 0;
for (UINT i = 0; i < memBuffer.RegionSize; ++i)
{
if (pFileMapAddr[i] != '\n')
{
byteTemp[uIndex++] = pFileMapAddr[i];
if (pFileMapAddr[i] == '\0')
{
UnmapViewOfFile(pFileMapAddr);
break;
}
}
else
{
wchar_t *pTemp = Utf8ToUnicode(byteTemp);
if (wcsstr(pTemp, L"1990") != nullptr)
{
++dwCount;
//wprintf(L"%s", pTemp);
}
uIndex = 0;
memset(byteTemp, 0, MAXLINESIZE);
delete[] pTemp;
}
}
UnmapViewOfFile(pFileMapAddr);
CloseHandle(hFileMap);
}
else
{
wchar_t wstrMessage[MAXBYTE] = { 0 };
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, wstrMessage, MAXBYTE, nullptr);
wprintf(L"文件映射失败,错误原因是:%s\r\n", wstrMessage);
}
CloseHandle(hFile);
}
else
{
// 错误处理
wchar_t wstrMessage[MAXBYTE] = { 0 };
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, wstrMessage, MAXBYTE, nullptr);
wprintf(L"打开文件失败,错误原因是:%s\r\n", wstrMessage);
}
DWORD dwCurTime = GetTickCount();
DWORD dwTimeElaped = dwCurTime - dwPrevTime;
wprintf(L"共找到:%d 条符合条件的记录, 用时:%d毫秒。\r\n", dwCount, dwTimeElaped);
}
void ReadFromMemory()
{
setlocale(LC_ALL, "chs");
wchar_t *wstrFilePath = L"1.csv";
DWORD dwPrevTime = GetTickCount();
DWORD dwCount = 0;
HANDLE hFile = CreateFile(wstrFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (INVALID_HANDLE_VALUE != hFile)
{
// 获取文件大小
LARGE_INTEGER largeInteger;
if (GetFileSizeEx(hFile, &largeInteger))
{
char *pFilldata = new char[largeInteger.QuadPart];
if (ReadFile(hFile, pFilldata, largeInteger.QuadPart, nullptr, nullptr))
{
BYTE byteTemp[MAXLINESIZE] = { 0 };
UINT uIndex = 0;
for (UINT i = 0; i < largeInteger.QuadPart; ++i)
{
if (pFilldata[i] != '\n')
{
byteTemp[uIndex++] = pFilldata[i];
if (pFilldata[i] == '\0')
{
break;
}
}
else
{
wchar_t *pTemp = Utf8ToUnicode(byteTemp);
if (wcsstr(pTemp, L"1990") != nullptr)
{
++dwCount;
}
uIndex = 0;
memset(byteTemp, 0, MAXLINESIZE);
delete[] pTemp;
}
}
delete[] pFilldata;
}
else
{
wchar_t wstrMessage[MAXBYTE] = { 0 };
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, wstrMessage, MAXBYTE, nullptr);
wprintf(L"读取文件内容失败,错误原因是:%s\r\n", wstrMessage);
}
}
else
{
wchar_t wstrMessage[MAXBYTE] = { 0 };
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, wstrMessage, MAXBYTE, nullptr);
wprintf(L"获取文件大小失败,错误原因是:%s\r\n", wstrMessage);
}
CloseHandle(hFile);
}
else
{
// 错误处理
wchar_t wstrMessage[MAXBYTE] = { 0 };
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0, wstrMessage, MAXBYTE, nullptr);
wprintf(L"打开文件失败,错误原因是:%s\r\n", wstrMessage);
}
DWORD dwCurTime = GetTickCount();
DWORD dwTimeElaped = dwCurTime - dwPrevTime;
wprintf(L"共找到:%d 条符合条件的记录, 用时:%d毫秒。\r\n", dwCount, dwTimeElaped);
}
int main()
{
wprintf(L"普通的文件读取方式,一个字节一个字节的读取:\r\n");
ReadFromFile();
wprintf(L"\r\n\r\n文件映射方式:\r\n");
ReadFromFileMap();
wprintf(L"\r\n\r\n一次性将整个内容读取到内存再处理的方式:\r\n");
ReadFromMemory();
system("pause");
return 0;
}
结果如下:
需要注意的是:
- wprintf函数输出宽字节的问题,如果我们直接输出宽字节的中文字符,它会输出乱码,此时就需要对编码进行设置,使用setlocale(LC_ALL, “chs”)函数来设置,就能正确输出中文字符;
- MultiByteToWideChar的Windows API函数的使用需要注意;
- 使用FileMap时,因为我的内存可以一次全部将322兆的文件读进来,但是如果碰到计算机内存很小的计算机,有可能一次就读不进去322兆的文件,上面的方法就会有问题。正确的方式应该分多次来读取,这个方法下次再介绍。
- 从三中结果来看,使用文件映射时间最短,而一个字节一个字节的读取时间最长,一次将整个文件读进到内存的时间略大于文件映射,我们几乎可以认为多出来的大概500毫秒的时间是将整个文件读到内存所用与文件映射所用时间的差,所以总的来说,文件映射还是很块的。其实我们可以单独做一个实验,将只读到内存和文件映射的耗费时间。
下面的结果是指读取,不处理
从结果看出,文件映射几乎是不需要时间的,所以在以后的工作中,我们可以将很多事情都用文件映射来处理,比如:画界面,我们可以把bmp直接加载到FileMap中,然后通过hdc来画FileMap中的数据。其实任何的数据都可以加载到FileMap中,但是也不要滥用,比如很小的文件就不用了。