搜索数千个字符串的大文本文件

时间:2013-05-03 14:07:50

标签: c++ string performance

我有一个大小为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天内完成搜索将是理想的,但任何显着的性能改进都会很好。我更喜欢使用带有Boost的标准C ++(如果需要),同时避免使用其他库。

所以我有两个问题:

  1. 考虑到我正在使用的工具和任务,4天的时间是否合理?
  2. 什么是让它更快的最佳方法?
  3. 感谢。

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

3 个答案:

答案 0 :(得分:3)

您描述的问题看起来更像是所选算法的问题,而不是选择的技术。在4天内完成20000次20000次全扫描听起来并不合理,但你的目标应该是20GB的单次扫描和20K字的另一次扫描。

您是否考虑过查看一些字符串匹配算法?想到Aho-Corasick。

答案 1 :(得分:3)

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

hand, has, have, foot, file

生成的树看起来像这样: Tree generated by the list of search terms

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

使用此结构,您可以通过从大文件中一次读取字符开始处理文件,然后走树。

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

这种技术会产生线性时间O(n)来检查匹配并只扫描一个巨大的20gb文件。

修改

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

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列表最多 必须检查的最长字的长度。因此,它不应该增加太多的复杂性。

答案 2 :(得分:0)

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