在不覆盖数据的情况下向文件中间写入数据

10

在Windows中是否有API可以在文件中间写入数据而不覆盖任何数据,也无需重写插入点后的所有数据?

如果可能的话,它显然会使文件碎片化;在出现严重问题之前,我能进行多少次这样的操作?

如果不可能,通常采取什么方法/解决方案?使用大型(即,千兆字节)文件时,重新编写插入点后的所有内容会很快变得难以实现。


注意:我无法避免对文件中间进行写入。请将应用程序视为用于巨大文件的文本编辑器,其中用户键入内容,然后保存。我也无法将文件拆分为几个较小的文件。

7个回答

8

如果您需要的中间结果是可以被编辑器以外其他应用程序使用的平面文件,我不知道任何方法可以实现。 如果您希望生成一个平面文件,则必须从更改点更新到文件末尾,因为它实际上只是一个连续的文件。

但是斜体字有其存在的原因。如果您可以控制文件格式,则有一些选项。某些版本的MS Word具有快速保存功能,它们不重新编写整个文档,而是将增量记录附加到文件末尾。然后,在重新读取文件时,按顺序应用所有增量,以便得到正确的文件。如果保存的文件必须立即供另一个不了解文件格式的应用程序使用,则显然无法使用该方法。

我的建议是不要将文件存储为文本。使用中间形式进行高效编辑和保存,然后执行一步操作,将其转换为可用的文本文件,例如在编辑器退出时执行。这样,用户可以随意保存,但耗费时间的操作对性能影响不会太大。

除此之外,还有一些其他可能性。

将文件进行内存映射(而不是加载)可能提供效率,从而加快速度。您可能仍然需要重写文件末尾,但这将在操作系统中的较低级别上发生。

如果您希望快速保存的主要原因是让用户继续工作(而不是让文件可用于另一个应用程序),则可以将保存操作分配给单独的线程,并立即将控制权返回给用户。然后,您需要在两个线程之间进行同步以防止用户修改尚未保存到磁盘的数据。


1
+1 内存映射;但在快速保存的 Word 文档等格式方面要小心:最终您将获得一个充满旧数据的巨大文件。这可能是一个问题,因为(1)它浪费磁盘空间,(2)用户认为已删除的数据仍将存在,因此一个表面上为空的文件仍可能包含敏感信息。我IRC,在 Office 的最新版本之一中出于这些原因(可能是 2003 年,但我不确定),Microsoft 默认关闭了快速保存功能:由于磁盘比以前快得多,这种技术的缺点超过了优点。 - Matteo Italia
我记得Word有一个阈值,超过这个阈值它会写入真实文件而不是另一个增量文件,这可以解决第一个问题。但你说得对,敏感数据确实存在,我曾经在文档中看到过一些本不应该被看到的东西 :-) - paxdiablo

4
现实的答案是否定的。你唯一的选择是从修改点开始重写,或者构建一个更复杂的格式,使用类似索引的东西告诉如何将记录排列到它们预期的顺序中。
从纯理论角度来看,在恰当的情况下,你可以在某种程度上做到这一点。例如,使用FAT(但大多数其他文件系统至少有一定程度的相似性),你可以进入并直接操作FAT。FAT基本上是文件组成部分的簇的链接列表。你可以修改该链接列表以在文件中间添加新的簇,然后将新数据写入你添加的那个簇。
请注意,我说的是纯理论。在完全不受保护的系统(如MS-DOS)下进行这种操作将会很困难,但几乎合理。对于大多数较新的系统,进行修改通常会非常困难。大多数现代文件系统也比FAT(显着)更复杂,这会增加实现的难度。理论上仍然可能 - 实际上,现在甚至考虑这种操作已经彻底疯狂,而曾经它几乎是合理的。

在现代操作系统中直接修改文件系统是愚蠢的:你必须理解多个文件系统的工作原理(相当困难),编写一个带有所需扩展功能的驱动程序,而IFS驱动程序对于“普通”驱动程序编写者来说也是黑魔法;此外,你将限制你的应用只能使用少数几种文件系统。所有这些只为了一个往往可忽略不计的性能改进。顺便说一下,如果插入的文本大小不符合簇的大小,那么就根本没有性能优势。 - Matteo Italia

3
我不确定您的文件格式,但您可以将其基于“记录”进行处理。
  • 将数据分块并为每个块分配一个id。
  • id可以是文件中的数据偏移量。
  • 在文件开头,您可以有一个标题,其中包含id列表,以便按顺序读取记录。
  • 在“id列表”的末尾,您可以指向文件中的另一个位置(和id/偏移量),该位置存储另一个id列表。

类似于文件系统。

要添加新数据,请将其附加到末尾并更新索引(将id添加到列表中)。

您必须想出如何处理删除记录和更新。

如果记录大小相同,则要删除,只需标记为空,并在下次使用适当更新索引表时重用它。


这是正确的答案。由于OP需要文件系统的功能,他需要在文件系统之上实现一个文件系统。您所描述的方法非常可用,并且非常容易实现(尽管我肯定会将索引移动到单独的文件并使其成为B-tree)。 - ScumCoder

1

如果你真的想这样做,可能最有效的方法是调用ReadFileScatter()来读取插入点前后的块,在FILE_SEGMENT_ELEMENT[3]列表的中间插入新数据,并调用WriteFileGather()。是的,这涉及在磁盘上移动字节。但你把难点留给了操作系统。


0
如果使用.NET 4,如果您有类似编辑器的应用程序,请尝试使用内存映射文件-可能是正确的选择。像这样的东西(我没有在VS中输入它,所以不确定语法是否正确):
MemoryMappedFile bigFile = MemoryMappedFile.CreateFromFile(
   new FileStream(@"C:\bigfile.dat", FileMode.Create),
       "BigFileMemMapped",
       1024 * 1024,
       MemoryMappedFileAccess.ReadWrite);
MemoryMappedViewAccessor view = MemoryMapped.CreateViewAccessor();
int offset = 1000000000;
view.Write<ObjectType>(offset, ref MyObject);

0

我注意到了paxdiablo关于处理其他应用程序的答案,以及Matteo Italia关于可安装文件系统的评论。这让我意识到还有另一个非平凡的解决方案。

使用重分析点,您可以从基本文件加上增量创建一个“虚拟”文件。任何不知道此方法的应用程序将看到一系列连续的字节,因为文件系统过滤器会即时应用增量。对于小增量(总计<16 KB),增量信息可以存储在重分析点本身中;而较大的增量则可以放置在替代数据流中。当然,这是一个非平凡的解决方案。


0

我知道这个问题标记为“Windows”,但是我仍然会加上我的$0.05并说,在Linux上可以在不留下空洞或将第二个半部分向前/向后复制的情况下插入或删除文件中间的一块数据:

fallocate(fd, FALLOC_FL_COLLAPSE_RANGE, offset, len)
fallocate(fd, FALLOC_FL_INSERT_RANGE, offset, len)

再次声明,我知道这可能对提问者没有帮助,但我个人是在寻找一个关于Linux的特定答案时才来到这里的。(问题中没有“Windows”一词,因此搜索引擎将我引到了这里。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接