【问题标题】:Reading huge amounts of small files in sequence依次读取海量小文件
【发布时间】:2009-10-13 14:18:20
【问题描述】:

我有这个问题:我有一个大约 2000 字节大的小文件的集合(它们的大小完全相同)并且大约有 100.000 个 em 大约等于 200 兆字节的空间。我需要能够实时选择这些文件中的范围。说文件 1000 到 1100(总共 100 个文件),读取它们并以相当快的速度通过网络发送它们。

好消息是文件将始终按顺序读取,即始终会是“从这个文件和一百多个文件”而不是“这个文件在这里,那个文件在那里等”的范围。 ”。

文件也可以在运行时添加到此集合中,因此它不是固定数量的文件。

我想出的当前方案是这样的:没有文件大于 2000 字节,因此我将拥有一个包含所有其他文件的大文件,而不是在磁盘上分配多个文件,甚至 2048 字节每个 2048 块的前 2 个字节的间隔是下一个 2046 字节中包含的文件的实际字节大小(文件大小在 1800 和 1950 字节之间左右),然后在这个文件中寻找而不是打开一个新文件处理我需要阅读的每个文件。

所以当我需要在位置 X 获取文件时,我将只执行 X*2048,读取前两个字节,然后从 (X*2048)+2 读取字节到前两个字节中包含的大小。这个 200mb 的大文件只会被追加,因此即使在序列化的输入线程/进程(尚未决定)向其追加更多数据时也可以安全读取。

这必须在 Windows 上可行,C 是一个选项,但我更喜欢 C#。

【问题讨论】:

    标签: c# .net windows filesystems


    【解决方案1】:

    您反对将这些文件存储在数据库中吗?

    一个简单的 RDBMS 将大大加快一堆 2k 文件的搜索和排序

    【讨论】:

    • 没有搜索或排序,它们以它们被附加的方式被索引,永远不会被重新排序或以任何其他方式被选择,而不是它们被放入的顺序。
    • 更不用说返回它们,(这就是您要寻找的)仍然比基于文件的方法更快。
    • 从技术上讲可以,但我看不出数据库会比 10*2048 更快到达正确的位置。
    • 因为除非您要保持一堆文件句柄持久化,否则 DB 就是为了做到这一点而设计的,并且比直接磁盘 IO 做得更好。
    • 保持文件句柄持久化并没有真正的问题,我可以有多个读取器和一个写入器,因为文件只是附加的,他们可以根据需要持有句柄。
    【解决方案2】:

    我认为你的想法可能是体面工作所能做到的最好的。

    或者,您可以购买固态磁盘而不关心文件大小。

    如果您不依赖于保持较低的 RAM 使用率(这也是最快的选择),您也可以将整个数据预加载到内存中的集合中。

    或者您可以使用数据库,但这里的开销会很大。

    【讨论】:

    • 是的,这实际上让我觉得这是一个可行的替代方案,使用本地内存应该会以不同的方式加快速度。我首先丢弃该版本的原因是,在原始数据中它应该是 2gb 的文件而不是 200mb 的文件(最大会增长到 400mb 左右,大约 200k 文件),但我也许可以把它塞进记忆。
    • @thr 您是否使用了FileSystemWatcher 或者您如何满足您的“文件可以随时随地添加”规范?
    【解决方案3】:

    这听起来是一个合理的选择。

    在读取范围的数据时,我很想寻找“数据块”的开头,然后将全部内容读入内存(即所有文件的 2048 字节缓冲区)去。这将使文件 IO 降至最低。

    在内存中获得所有数据后,您可以解码大小并仅发送真实数据位。

    将所有内容加载到内存中可能是个好主意,但这完全取决于修改频率和查询频率。

    除了“这是一件理智的事情吗”之外,这个问题还有什么其他的吗?

    【讨论】:

    • 啊,是的,关于将整个范围读入内存然后将其解码为适当的部分的好点。好吧,我的问题是,如果有比我想出的更快的方法来做到这一点。
    【解决方案4】:

    您确定永远不想删除从 1200 到 1400 的文件吗?转移完成后会发生什么?数据是存档还是会持续增长?

    我真的不明白为什么将所有数据附加到单个文件会提高性能。相反,它可能会给您带来更多问题。那么,为什么要将它们结合起来呢?

    其他需要考虑的事情是,如果大量文件在中间因磁盘上的坏扇区而损坏,会发生什么情况?看起来你失去了一切。将它们分开应该会增加它们的生存能力。

    您当然可以在不将整个内容加载到内存中的情况下处理大文件,但这并不容易,您最终必须降级到一些低级编码才能做到这一点。不要约束自己。另外,如果文件需要一些手工编辑怎么办?大多数程序会强制您加载并锁定整个程序。

    此外,拥有一个大文件意味着您不能让多个进程读取/写入数据。这限制了可扩展性。

    如果您知道需要从 #1000 到 1100 的文件,则可以使用内置 (c#) 代码来获取满足该条件的文件集合。

    【讨论】:

    • 好点,但我很确定约束:文件永远不会被删除,只会附加。这:“此外,拥有一个大文件意味着您不能让多个进程读取/写入数据。这限制了可伸缩性。”但这不是真的,你不能有多个作家(或者你可以,但很难做到正确) - 但是你可以有多个读者和一个作家,只要你只附加读者。
    【解决方案5】:

    您可以简单地将所有文件连接到一个大文件“dbase”中,无需任何页眉或页脚。

    在另一个文件'index'中,您可以将所有小文件的位置保存在'dbase'中。这个索引文件,很小,可以完全缓存在内存中。

    此方案允许您快速读取所需文件,并在您的集合末尾添加新文件。

    【讨论】:

      【解决方案6】:

      你的计划听起来可行。似乎文件流可以执行您需要的查找和读取。您是否遇到了具体的实施问题,或者您是否正在寻找更好的方法?

      是否有更好的方法可能取决于您读取文件的速度与在网络上传输文件的速度。假设您可以比发送它们更快地读取大量单个文件,也许您可​​以设置一个有界缓冲区,您可以在其中将 x 个文件提前读取到队列中。另一个线程将从队列中读取并在网络上发送它们

      【讨论】:

      • 好点,我会评估这个。不过,现在我倾向于将它们全部放在内存中,应该是最快的,而且我不必经历检查“文件”的麻烦。
      【解决方案7】:

      我会以一种方式修改您的方案:不是读取前两个字节,然后使用它们来确定下一次读取的大小,而是立即读取 2KiB,然后使用前两个字节来确定有多少您传输的字节数。

      与避免将最后约 150 个字节从磁盘传输到内存相比,仅使用一次磁盘读取可能会节省更多时间。

      另一种可能性是将文件的数据打包在一起,并维护一个单独的索引来告诉您每个文件的起始位置。对于您的情况,这样做的好处是,您可以将任意数字组合成一次大读取,而不是从磁盘执行大量小 (2K) 读取。每次读取大约 64-128K 通常会节省大量时间。

      【讨论】:

      • 也是一个好主意,我认为您的想法和 john skeet 所说的结合是最好的,如果我需要文件 1000-1100,只需从 1000*2048 读取到 1100*2048 然后对其进行排序当所有内容都存在时,内存中只使用一个完整的磁盘读取所有内容(如您建议的那样,但没有打包文件)
      【解决方案8】:

      您可以坚持使用一个大文件的解决方案,但使用内存映射来访问它(例如,参见 here)。这可能会更高效一些,因为您还避免了分页,并且虚拟内存管理针对传输 4096 字节的块进行了优化。 Afaik,没有直接支持内存映射,但here 是一些如何为 C# 包装 WIN32 API 调用的示例。

      有关 SO 的相关问题,另请参阅 here

      【讨论】:

        【解决方案9】:

        有趣的是,这个问题让我想起了这个较旧的 SO 问题中的问题:

        Is this an over-the-top question for Senior Java developer role?

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-09-27
          • 2022-01-11
          • 2023-03-14
          • 2017-04-25
          • 1970-01-01
          • 2018-03-26
          • 2020-04-27
          相关资源
          最近更新 更多