【问题标题】:Search Large Text File for Thousands of strings在大型文本文件中搜索数千个字符串
【发布时间】:2013-04-27 23:20:55
【问题描述】:

我有一个 20 GB 的大文本文件。该文件包含相对较短的文本行(每行 40 到 60 个字符)。文件未排序。

我有一个包含 20,000 个唯一字符串的列表。我想知道每个字符串每次出现在文件中时的偏移量。目前,我的输出如下所示:

netloader.cc found at offset: 46350917
netloader.cc found at offset: 48138591
netloader.cc found at offset: 50012089
netloader.cc found at offset: 51622874
netloader.cc found at offset: 52588949
...
360doc.com found at offset: 26411474
360doc.com found at offset: 26411508
360doc.com found at offset: 26483662
360doc.com found at offset: 26582000

我将 20,000 个字符串加载到 std::set 中(以确保唯一性),然后从文件中读取 128MB 块,然后使用 string::find 搜索字符串(通过读取另一个 128MB 块重新开始)。这工作并在大约 4 天内完成。我不担心读取边界可能会破坏我正在搜索的字符串。如果是这样,那没关系。

我想让它更快。在 1 天内完成搜索将是理想的,但任何显着的性能改进都会很好。我更喜欢将标准 C++ 与 Boost(如果需要)一起使用,同时避免使用其他库。

所以我有两个问题:

  1. 考虑到我使用的工具和任务,4 天的时间是否合理?
  2. 加快速度的最佳方法是什么?

谢谢。

编辑:使用 Trie 解决方案,我能够将运行时间缩短到 27 小时。不是一天之内,但现在肯定要快得多。谢谢你的建议。

【问题讨论】:

  • 这些字符串是否看起来像单个单词或标识符,而不是整个句子,用空格等分隔?
  • 您是否尝试过分析您的代码?它是否花费更多时间从输入文件中搜索或读取?
  • 读取 20Gb 不能花 4 天...
  • @piokuc,你说得对,但我认为他正在读取 20,000 次,意味着总共读取了大约 390TB。我的建议是,如果已知可用 RAM,则将文件拆分为相当大的块,在每个块中搜索字符串,转储块,然后继续。不过,他检查字符串的方法有很大的不同。
  • 他说他正在读取 128MB 块并在一个块中进行 20k 次搜索,然后再转到下一个块,这就是我的理解。

标签: c++ string performance


【解决方案1】:

从算法上讲,我认为解决此问题的最佳方法是使用树来存储一次要搜索字符的行。例如,如果您有以下想要查找的模式:

hand, has, have, foot, file

生成的树看起来像这样:

树的生成是最坏情况O(n),并且通常具有亚线性内存占用。

使用这种结构,您可以通过从大文件中一次读取一个字符来开始处理您的文件,然后遍历树。

  • 如果您到达叶节点(以红色显示的节点),则您已找到匹配项,并且可以存储它。
  • 如果没有子节点,对应你有红色的字母,你可以丢弃当前行,从树根开始检查下一行

这种技术将导致线性时间 O(n) 来检查匹配并仅扫描一次巨大的 20gb 文件。

编辑

上述算法肯定是合理(它不会给出误报),但不是完整(它可能会遗漏一些结果)。但是,假设我们没有像 gogone 这样具有共同词根的搜索词,只需进行一些小的调整就可以完成。以下是完整版算法的伪代码

tree = construct_tree(['hand', 'has', 'have', 'foot', 'file'])
# Keeps track of where I'm currently in the tree
nodes = []
for character in huge_file:
  foreach node in nodes:
    if node.has_child(character):
      node.follow_edge(character)
      if node.isLeaf():
        # You found a match!!
    else:
      nodes.delete(node)
  if tree.has_child(character):
    nodes.add(tree.get_child(character))

请注意,每次都必须检查的nodes 列表最多是必须检查的最长单词的长度。因此它不应该增加太多的复杂性。

【讨论】:

  • +1 这可能比 Nico 的建议 (Aho-Corasick) 更容易实现,并且仍然比当前方法产生巨大的速度改进。顺便说一句,很好的解释。
  • 当然你可以一次读取块,你只需要一次检查块的一个字符,这是在RAM中顺序完成的,所以这对IO来说应该不会很重。
【解决方案2】:

您描述的问题看起来更像是所选算法的问题,而不是所选技术的问题。在 4 天内对 20GB 进行 20000 次完整扫描听起来并不太合理,但您的目标应该是对 20GB 进行一次扫描,再对 20K 字进行一次扫描。

您是否考虑过一些字符串匹配算法? Aho-Corasick 浮现在脑海中。

【讨论】:

    【解决方案3】:

    您可以尝试对输入进行标记化并在您的std::set 中查找要找到的字符串,而不是单独搜索 20,000 次,这样会快得多。这是假设您的字符串是简单的标识符,但是对于作为句子的字符串可以实现类似的东西。在这种情况下,您将在每个句子中保留一组第一个单词,并在成功匹配后验证它确实是整个句子的开头string::find

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-21
      • 2010-12-10
      相关资源
      最近更新 更多