设计:大型档案文件编辑器,文件映射
Design: Large archive file editor, file mapping
我正在为4GB+的大型归档文件(见下文)编写一个编辑器,使用本机&托管C++。
为了访问文件,我像任何理智的人一样使用文件映射(见下文)。这对于读取数据来说是非常好的,但在实际编辑档案时会出现问题。文件映射不允许在访问文件时调整文件大小,所以当用户想在文件中插入新数据时,我不知道该如何处理(这将超过文件映射时的原始大小)
我应该每次都重新映射整个东西吗?这肯定会很慢。然而,我希望通过独占文件访问来保持编辑器的实时性,因为这大大简化了编程,并且不会让文件在修改时被其他应用程序搞砸。我不想花太多时间在编辑身上;这只是我正在进行的实际项目的一个简单的开发工具
所以我想听听你是如何处理类似案件的,还有其他什么存档软件,尤其是其他游戏可以解决这个问题?
澄清:
-
这不是一个文本文件,我正在编写一个特定的二进制存档文件格式。我指的是一个包含许多其他文件的大文件,在目录中。由于多种原因,自定义存档文件在游戏使用中非常常见。对于我的格式,我的目标是使用与Valve Software的GCF格式类似(但稍微简单一点)的结构-我本可以按原样使用GCF格式,但不幸的是,该格式没有编辑器,尽管有很多很好的实现可以阅读它们,如HLLib。
-
访问该文件必须快速,因为它用于存储游戏资源。所以它不是一个数据库。数据库文件将包含在其中,以及GFX、SFX等文件。
-
这里所说的"文件映射"是Windows平台上的一种特定技术,它允许通过创建部分文件的"视图"来直接访问大文件,请参阅此处:http://msdn.microsoft.com/en-us/library/aa366556(VS.85).aspx-这项技术可以将延迟和内存使用降至最低,而且对于访问任何大型文件来说都是一件轻而易举的事。因此,并不意味着将整个4GB文件读入内存,恰恰相反。
"编辑器软件"是什么意思?如果这是一个文本文件,在编写自己的编辑器之前,您是否尝试过现有的生产质量编辑器?如果它是一个存储二进制数据的文件,您是否考虑过使用RDBMS并使用SQL语句操作其内容?
如果你必须从头开始写这篇文章,我不确定mmapping是否可行。映射一个巨大的文件会给机器的虚拟机系统带来很大的压力,除非整个文件都有很多编辑操作,否则它的效率可能落后于简单的读/写方案。更糟糕的是,正如您所说,当您想要扩展文件时,您会遇到问题。
相反,维护文件数据的缓冲区窗口,用户可以对其进行修改。当用户决定保存文件时,按顺序遍历文件和编辑过的缓冲区以创建新的文件图像。如果你有磁盘空间,那么写一个新文件会更容易(尤其是在缓冲区大小发生变化的情况下),否则,在用新内容覆盖现有数据之前,你需要聪明地预读现有数据。
或者,您可以保留编辑操作的日志。当用户决定保存文件时,对日志执行拓扑排序,并在现有文件上播放以创建新文件。
对于独占文件访问,请使用操作系统的文件锁定或实现应用程序级锁定(如果只有编辑器会接触这些文件)。依赖mmap进行独占访问会限制您的实现选择。
映射文件是为实际访问数据而创建的,但我认为您需要另一个表示文件结构的抽象。有多种方法可以做到这一点,但请考虑将文件表示为"扩展数据块"序列。
从文件开始,是相当于整个映射的单个扩展区。如果用户随后开始编辑文件,则可以在编辑点将单个数据块拆分为两个,并插入包含用户插入的数据的新数据块。修改和删除也会通过创建或修改这些扩展区来修改文件的视图。
也许你可以为一个开源编辑器检查源代码——有很多选择,但找到一个足够简单的编辑器将是一个挑战。
我所做的是关闭视图句柄和FileMapping句柄,设置文件大小,然后重新打开映射/视图句柄。
// Open memory mapped file
HANDLE FileHandle = ::CreateFileW(file_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
size_t Size = ::GetFileSize(FileHandle, 0);
HANDLE MappingHandle = ::CreateFileMapping(FileHandle, NULL, PAGE_READWRITE, 0, Size, NULL);
void* ViewHandle = ::MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, Size);
...
// increase size of file
UnmapViewOfFile(ViewHandle);
CloseHandle(MappingHandle);
Size += 1024;
LARGE_INTEGER offset;
offset.QuadPart = Size;
LARGE_INTEGER newpos;
SetFilePointerEx(FileHandle, offset, &newpos, FILE_BEGIN);
SetEndOfFile(FileHandle);
MappingHandle = ::CreateFileMapping(FileHandle, NULL, PAGE_READWRITE, 0, Size, NULL);
ViewHandle = ::MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, Size);
上面的代码没有错误检查,也不处理64位大小,但这并不难修复。
这个问题没有简单的答案——我已经找了很长时间了,但没有找到。您必须修改文件的大小,然后重新映射。
映射在远程系统上存在文件的基本问题。
在DOS时代,有一位优秀的编辑叫Norton editor(ne.com文件名,而不是网站)。它可以加载任何大小的文件(我们说的是640kb的RAM以及20GB硬盘(如果有的话)。
它过去只加载文件的一部分,通过随需应变巧妙地管理文件长搜索加载
IMHO,应该使用这样的方法。
如果正确地隐藏在文件读写层下,它可能会非常透明。
我会在构建时用碎片构建大文件。您让编辑器在通常的文件系统中处理普通的平面文件(视情况而定,包括子目录等)。然后,您有一个编译步骤,将所有这些部分汇集到归档文件格式中。
- GCC链接器 - 将存档中的所有对象文件映射到特定部分
- 为什么我无法使用文本文件创建文件映射?
- 如何通过文件映射对象重新映射共享内存的视图?
- 共享数据文件映射
- 频繁访问文件映射内存
- 如何在Windows和更高版本上保留内存,并将文件映射到内存中
- 文件映射对象和文件对象可以互换使用吗
- 如何在C++中创建文件映射
- 使用文件映射从文件中读取数据
- 打开文件映射问题,找不到文件映射
- 将几个大文件映射到内存
- 文件映射和文件结构
- Windows CreateFileMapping:具有相同支持文件的不同文件映射对象
- 文件映射IPC挂起MapViewOfFile调用
- 使用C++中的文件映射对象进行读取
- 如何正确使用文件映射并将数据传递给子进程
- 文件映射打开(Windows)
- 循环文件映射会降低性能
- 内存映射文件-映射结构而不是文件
- 将一个文件映射到内存,然后获取到该内存的文件描述符