设计:大型档案文件编辑器,文件映射

Design: Large archive file editor, file mapping

本文关键字:文件 映射 编辑器 档案文件 大型 设计      更新时间:2023-10-16

我正在为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,应该使用这样的方法。

如果正确地隐藏在文件读写层下,它可能会非常透明。

我会在构建时用碎片构建大文件。您让编辑器在通常的文件系统中处理普通的平面文件(视情况而定,包括子目录等)。然后,您有一个编译步骤,将所有这些部分汇集到归档文件格式中。