使用 MapViewOfFile 映射大文件
Posted
技术标签:
【中文标题】使用 MapViewOfFile 映射大文件【英文标题】:Mapping large files using MapViewOfFile 【发布时间】:2012-04-10 23:39:09 【问题描述】:我有一个非常大的文件,我需要将它分成小块读取,然后处理每一块。我正在使用 MapViewOfFile 函数在内存中映射一块,但在阅读第一部分后我无法阅读第二部分。当我试图映射它时它会抛出。
char *tmp_buffer = new char[bufferSize];
LPCWSTR input = L"input";
OFSTRUCT tOfStr;
tOfStr.cBytes = sizeof tOfStr;
HANDLE inputFile = (HANDLE)OpenFile(inputFileName, &tOfStr, OF_READ);
HANDLE fileMap = CreateFileMapping(inputFile, NULL, PAGE_READONLY, 0, 0, input);
while (offset < fileSize)
long k = 0;
bool cutted = false;
offset -= tempBufferSize;
if (fileSize - offset <= bufferSize)
bufferSize = fileSize - offset;
char *buffer = new char[bufferSize + tempBufferSize];
for(int i = 0; i < tempBufferSize; i++)
buffer[i] = tempBuffer[i];
char *tmp_buffer = new char[bufferSize];
LPCWSTR input = L"input";
HANDLE inputFile;
OFSTRUCT tOfStr;
tOfStr.cBytes = sizeof tOfStr;
long long offsetHigh = ((offset >> 32) & 0xFFFFFFFF);
long long offsetLow = (offset & 0xFFFFFFFF);
tmp_buffer = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, bufferSize);
memcpy(&buffer[tempBufferSize], &tmp_buffer[0], bufferSize);
UnmapViewOfFile(tmp_buffer);
offset += bufferSize;
offsetHigh = ((offset >> 32) & 0xFFFFFFFF);
offsetLow = (offset & 0xFFFFFFFF);
if (offset < fileSize)
char *next;
next = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, 1);
if (next[0] >= '0' && next[0] <= '9')
cutted = true;
UnmapViewOfFile(next);
ostringstream path_stream;
path_stream << tempPath << splitNum;
ProcessChunk(buffer, path_stream.str(), cutted, bufferSize);
delete buffer;
cout << (splitNum + 1) << " file(s) sorted" << endl;
splitNum++;
【问题讨论】:
如果您从一个大文件串行处理数据,那么使用 MMF 只会减慢它的速度。文件系统缓存已经实现了文件内容的内存视图。仅使用 MMF 来优化随机访问。 您应该查看 GetLastError() 错误代码。它绝对应该说明失败的原因 这个代码示例有很多问题。它甚至不会按原样编译。请将相关代码复制到新项目中,尽量简化,然后贴出完整的、可编译的单元,就像函数一样。 【参考方案1】:您的代码中存在内存泄漏:
char *tmp_buffer = new char[bufferSize];
[ ... ]
while (offset < fileSize)
[ ... ]
char *tmp_buffer = new char[bufferSize];
[ ... ]
tmp_buffer = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, bufferSize);
[ ... ]
在每次迭代期间,您永远不是 delete
通过 new char[]
分配的东西。如果您的文件足够大/您对该循环进行了足够多的迭代,则内存分配最终将失败 - 然后您将看到分配器完成了 throw()
。
像MapViewOfFile()
这样的Win32 API 调用不是C++ 并且从不抛出,它们返回错误代码(后者在失败时NULL
)。因此,如果您看到异常,则说明您的 C++ 代码有问题。可能是上述情况。
【讨论】:
我正在使用 UnmapViewOfFile 删除它,它会删除指向 tmp_buffer 的指针 @AlexanderTaraymovich:不,它不支持 C++delete
。它撤消了MapViewOfFile()
的效果——那是不一样的。您已经“忘记”(丢弃/覆盖)了从 new
获得的缓冲区地址以及从 MapViewOfFile()
获得的映射地址,这就是为什么 - 泄漏。【参考方案2】:
一种可能性是您使用的偏移量不是分配粒度的倍数。来自 MSDN:
高偏移量和低偏移量的组合必须在文件映射中指定偏移量。它们还必须与系统的内存分配粒度相匹配。也就是说,偏移量必须是分配粒度的倍数。获取系统的内存分配粒度,使用GetSystemInfo函数,填充SYSTEM_INFO结构的成员。
如果您尝试在分配粒度的倍数之外进行映射,则映射将失败,GetLastError
将返回 ERROR_MAPPED_ALIGNMENT
。
除此之外,代码示例中存在许多问题,使您很难看到您正在尝试做什么以及哪里出了问题。至少,您需要解决内存泄漏问题。您似乎正在分配然后泄漏完全不必要的缓冲区。给它们起更好的名字可以明确它们的实际用途。
然后我建议在对 MapViewOfFile 的调用上放置一个断点,然后检查您传入的所有参数值以确保它们看起来正确。首先,在第二次调用时,您希望 offsetHigh 为 0,offsetLow 为 bufferSize。
一些可疑的事情:
HANDLE inputFile = (HANDLE)OpenFile(inputFileName, &tOfStr, OF_READ);
每个演员都应该让你怀疑。有时它们是必要的,但请确保您了解原因。此时,您应该问自己,为什么您使用的所有其他文件 API 都需要 HANDLE
,而此函数返回 HFILE
。如果您查看OpenFile documentation,您会看到“此功能功能有限,不推荐使用。对于新的应用程序开发,请使用 CreateFile 功能。”我知道这听起来令人困惑,因为您想打开一个现有文件,但 CreateFile 可以做到这一点,并且它返回正确的类型。
long long offsetHigh = ((offset >> 32) & 0xFFFFFFFF);
offset
是什么类型?您可能想确保它是 unsigned long long
或等效的。在进行位移时,尤其是向右位移时,您几乎总是希望使用无符号类型来避免符号扩展。您还必须确保它是一种比您要移位的位数更多的类型——将 32 位值移位 32(或更多)位实际上在 C 和 C++ 中是未定义的,这允许编译器进行某些类型的优化。
long long offsetLow = (offset & 0xFFFFFFFF);
在这两个语句中,您必须小心0xFFFFFFFF
值。由于您没有强制转换它或给它一个后缀,因此很难预测编译器会将它视为 int 还是 unsigned int。在这种情况下,
这将是一个无符号整数,但这对很多人来说并不明显。实际上,
我第一次写这个答案时弄错了。 [本段于 2017 年 5 月 16 日更正] 对于按位运算,您几乎总是希望确保使用无符号值。
tmp_buffer = (char *)MapViewOfFile(fileMap, FILE_MAP_READ, (int)offsetHigh, (int)offsetLow, bufferSize);
您将offsetHigh
和offsetLow
转换为int
s,它们是有符号值。 API 实际上需要DWORD
s,它们是无符号值。我不会在调用中进行强制转换,而是将 offsetHigh
和 offsetLow
声明为 DWORD
s 并在初始化中进行强制转换,如下所示:
DWORD offsetHigh = static_cast<DWORD>((offset >> 32) & 0xFFFFFFFFul);
DWORD offsetLow = static_cast<DWORD>( offset & 0xFFFFFFFFul);
tmp_buffer = reinterpret_cast<const char *>(MapViewOfFile(fileMap, FILE_MAP_READ, offsetHigh, offsetLow, bufferSize));
这些修复可能会也可能不会解决您的问题。从不完整的代码示例中很难判断发生了什么。
这是一个您可以比较的工作示例:
// Calls ProcessChunk with each chunk of the file.
void ReadInChunks(const WCHAR *pszFileName)
// Offsets must be a multiple of the system's allocation granularity. We
// guarantee this by making our view size equal to the allocation granularity.
SYSTEM_INFO sysinfo = 0;
::GetSystemInfo(&sysinfo);
DWORD cbView = sysinfo.dwAllocationGranularity;
HANDLE hfile = ::CreateFileW(pszFileName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hfile != INVALID_HANDLE_VALUE)
LARGE_INTEGER file_size = 0;
::GetFileSizeEx(hfile, &file_size);
const unsigned long long cbFile =
static_cast<unsigned long long>(file_size.QuadPart);
HANDLE hmap = ::CreateFileMappingW(hfile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hmap != NULL)
for (unsigned long long offset = 0; offset < cbFile; offset += cbView)
DWORD high = static_cast<DWORD>((offset >> 32) & 0xFFFFFFFFul);
DWORD low = static_cast<DWORD>( offset & 0xFFFFFFFFul);
// The last view may be shorter.
if (offset + cbView > cbFile)
cbView = static_cast<int>(cbFile - offset);
const char *pView = static_cast<const char *>(
::MapViewOfFile(hmap, FILE_MAP_READ, high, low, cbView));
if (pView != NULL)
ProcessChunk(pView, cbView);
::CloseHandle(hmap);
::CloseHandle(hfile);
【讨论】:
可能,使用更好的ULARGE_INTEGER 偏移?cpp ::ULARGE_INTEGER _off; _off.QuadPart = offset; ::MapViewOfFile( hmap, FILE_MAP_READ, _off.HighPart, _off.LowPart, cbView );
【参考方案3】:
我也遇到了一些内存映射文件的问题。 基本上我只是想在同一台电脑上的 2 个应用程序之间共享内存(1Mo)。 - 用 Delphi 编写的两个应用程序 - 使用 Windows8 专业版
第一个应用程序(第一个启动的)可以读写memoryMappedFile
,但第二个应用程序只能读取它(error 5 : AccessDenied
)
终于经过大量测试,当两个应用程序都使用CreateFileMapping
时,它突然起作用了。我什至尝试创建我的安全描述符,但没有任何帮助。
就在我的应用程序之前,首先调用OpenFileMapping
,如果第一个调用失败,然后调用CreateFileMapping
误导我的另一件事是,虽然在两个应用程序中明显引用了相同的 MemoryMappedFile
,但句柄是不同的。
最后一件事,经过这次更正后,我的应用程序似乎工作正常,但过了一段时间我出现了 error_NotEnough_Memory。调用 MapViewOfFile 时。 这只是我的一个初学者的错误,我并不总是调用 UnmapViewOfFile。
【讨论】:
以上是关于使用 MapViewOfFile 映射大文件的主要内容,如果未能解决你的问题,请参考以下文章
delphi 大文件的读写 使用 MapviewOffile