【问题标题】:Searching for string in massive files efficiently高效搜索海量文件中的字符串
【发布时间】:2013-03-02 07:23:48
【问题描述】:

我发现了这个想法的变体,但没有一个能让我(对 python 非常陌生)到达我需要的地方。

这是场景:

  1. 我有一个巨大的 27 gig hashfile.txt 由单独的字符串组成。
  2. 我需要逐行解析这个文件,在另一个不太大 (~800mb) addresses.txt 文件中搜索匹配项
  3. 找到匹配项后,需要将其写入outfile.txt

我当前的代码已尽我所能优化,但只能达到 150 行/秒左右。考虑到我的 hashfile.txt 中有超过 15 亿行,任何优化都会有所帮助。

fin = 'hashed.txt'
nonzeros = open('addrOnly.txt', 'r')
fout = open('hits.txt', 'w')
lines = nonzeros.read()
i = 0
count = 0

with open(fin, 'r') as f:
    for privkey in f:
            address = privkey.split(", ")[0]
            if address in lines:
                    fout.write(privkey)
            i = i+1
            if i%100 == 0:
                    count = count + 100
                    print "Passed: " + str(count)

【问题讨论】:

  • 您当前的代码是什么?这些文件是什么样的?
  • 具体来说,您能描述一下您当前的算法吗?
  • @tor:大概这将是一个 IO-bound 问题(尽管这将取决于算法的选择),因此在这种情况下使用的特定语言不太可能产生如此大的影响。
  • @tor 这是一个极端的概括。请记住,像str.find() 这样的许多字符串函数是用 C(在 CPython 中)实现的,并且速度非常快。在我使用-O3 编译之前,我所做的一个实用程序的 python 模型实际上比我的 C 实现高了约 10%。
  • 不要使用列表进行查找,使用具有 O(1) 时间复杂度的东西,比如字典。 lines = dict.fromkeys(nonzeros.read().split("\n"), 1)编辑也不要使用大字符串进行查找。

标签: python search optimization large-files


【解决方案1】:

您要实现的可能是Rabin-Karp string search。当您在某个语料库中同时搜索多个字符串时,它的效率很高。

在这篇文章中有关 python 实现的更多信息。 python efficient substring search

由于您要同时搜索多个地址,您可能希望对 addresses.txt 中的条目进行哈希处理,并在每次迭代时将它们与 Rabin-Karp 哈希值进行一次比较。阅读有关 Rabin-Karp 中滚动哈希的更多信息,您将了解其工作原理。

由于 Rabin-Karp 要求所有模式的长度相同;实际上,所有地址的长度可能都不可忽略,您可以将它们全部截断到相同(不太短)的长度并使用前缀进行哈希。此外,您可能希望修改 Rabin-Karp 散列,使其对空格和地址格式的细微差异保持不变,并类似地定义一个自定义字符串比较器以确认匹配。

【讨论】:

  • 我怀疑 27GB 的搜索字符串,即使经过哈希处理,也不太可能适合内存。
  • @davidg addresses.txt 是 800MB,根据 OP。
  • @JonathonReinhart 在我之前回复了 :)
  • @sberry 提出了一个很好的短期解决方案,这样我就可以上床睡觉了,但这是一种深入的回答,可以让我学到很多新东西——谢谢!
  • 我不认为你可以在搜索不同长度的字符串时应用 vanilla rabin-karp。虽然可能有一些巧妙的方法可以解决这个问题,但这似乎是一个不平凡的问题。
【解决方案2】:

有了这样的数据大小,我会使用合适的数据库。数据库针对大型数据集的快速处理进行了优化,比人们编写的 Python 程序要好得多。

直接字符串比较是昂贵的。让我们对字符串进行哈希处理,以便哈希的完整 二叉树索引 有很好的机会放入内存中。 md5 是 128 位的,计算速度非常快。

首先,为任一文件中的每条记录计算 md5,并将它们存储在另一个文本文件中:

from hashlib import md5
with open('hashfile.txt') as input:
  with open('hashfile-md5.txt', 'w') as output:
    for line in input:
      value = line.rstrip() # cut '\n'
      output.write(value)
      output.write('\t') # let our file be tab-separated
      output.write(int(value).hexdigest(), 16)) # md5 as long number
      output.write('\n')

address.txt 重复相同的操作,生成address-md5.txt

使用 Postgresql、mysql 甚至 SQLite(我将在这里使用),并创建两个表和一个索引。

$ sqlite3 matching-db.sqlite

create table hashfile (
  txt varchar(64), -- adjust size to line lengths of hashfile.txt
  hash number(38) -- enough to contain 128-bit hash
);

create table address (
  txt varchar(64), -- adjust size to line lengths of address.txt
  hash number(38) -- enough to contain 128-bit hash
);

现在加载我们的数据。本地数据库导入通常比通过 dbapi 从 Python 插入要快得多。

.separator \t
.import hashfile-md5.txt hashfile
.import address-md5.txt address

现在我们可以创建索引了:

create index x_address_hash on address(hash);

这是一个select 语句,它将有效地扫描大型hashfile 表并从小型address 表中查找匹配的哈希值。索引将一直在 RAM 中(希望如此),地址表中的大部分也是如此。

select h.txt
from hashfile h, address a
where h.hash = a.hash and h.txt = a.txt;

这个想法是索引x_address_hash 将用于有效地匹配散列,如果散列匹配,也会比较实际的文本值。

我没有在 29 MB 的数据上尝试过,但在玩具 2 行示例上它有效:)

【讨论】:

  • 我问这个已经好几年了,但这是一个非常彻底和伟大的答案。感谢您花时间写下来;我正在回顾我在这里处理的原始问题,并计划使用某种关系数据库来实现它。
猜你喜欢
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-22
  • 1970-01-01
  • 2019-04-23
  • 1970-01-01
  • 1970-01-01
  • 2014-03-09
相关资源
最近更新 更多