在大型文本文件中搜索字符串-在python中分析各种方法
这个问题已经被问过很多次了。在花了一些时间阅读答案后,我做了一些快速分析,以尝试前面提到的各种方法在大型文本文件中搜索字符串-在python中分析各种方法,python,performance,search,profiling,large-files,Python,Performance,Search,Profiling,Large Files,这个问题已经被问过很多次了。在花了一些时间阅读答案后,我做了一些快速分析,以尝试前面提到的各种方法 我有一个600MB文件,其中包含600万行字符串(DMOZ项目的类别路径) 每行上的条目都是唯一的 我想加载文件一次,继续搜索数据中的匹配项 下面我尝试的三种方法列出了加载文件所需的时间、负匹配的搜索时间以及任务管理器中的内存使用情况 加载时间~10s,搜索时间~0.0s,内存使用量~1.2GB 加载时间~6s,搜索时间~0.36s,内存使用量~1.2GB 加载时间~0s,搜索时间~5.
- 我有一个600MB文件,其中包含600万行字符串(DMOZ项目的类别路径)李>
- 每行上的条目都是唯一的
- 我想加载文件一次,继续搜索数据中的匹配项
加载时间~10s,搜索时间~0.0s,内存使用量~1.2GB
加载时间~6s,搜索时间~0.36s,内存使用量~1.2GB
加载时间~0s,搜索时间~5.4s,内存使用~NA
加载时间~65秒,搜索时间~0.0秒,内存使用量~250MB
加载时间~0s,搜索时间~3.2s,内存使用~NA
加载时间~0s,搜索时间~0.0s,内存使用~NA
对于我的用例来说,只要我有足够的可用内存,使用集合似乎是最好的选择。我希望得到一些关于这些问题的评论:
解决方案:基于所有的评测和反馈,我想我会选择sqlite。第二种选择是方法4。sqlite的一个缺点是,数据库大小是原始csv文件的两倍以上,其中包含URL。这是因为url上的主索引那么文本索引解决方案呢 我会在Java世界中使用Lucene,但是有一个python引擎叫做Whoosh
使用外部化字符串的自定义哈希表搜索 要获得快速访问时间和较低的内存消耗,您可以执行以下操作:
- 对于每一行,计算一个字符串哈希并将其添加到哈希表中,例如,
(不要存储该字符串)。如果发生冲突,请将该键的所有文件位置存储在列表中李>index[hash]=position
- 要查找字符串,请计算其哈希并在表中查找。如果找到密钥,请从文件中读取
,以验证是否确实匹配。如果有多个位置,请检查每个位置,直到找到匹配或没有位置处的字符串
from collections import namedtuple
Node = namedtuple('Node', ['pos', 'next'])
def build_table(f, size):
table = [ None ] * size
while True:
pos = f.tell()
line = f.readline()
if not line: break
i = hash(line) % size
if table[i] is None:
table[i] = pos
else:
table[i] = Node(pos, table[i])
return table
def search(string, table, f):
i = hash(string) % len(table)
entry = table[i]
while entry is not None:
pos = entry.pos if isinstance(entry, Node) else entry
f.seek(pos)
if f.readline() == string:
return True
entry = entry.next if isinstance(entry, Node) else None
return False
SIZE = 2**24
with open('data.txt', 'r') as f:
table = build_table(f, SIZE)
print search('Some test string\n', table, f)
行的散列仅用于索引到表中(如果我们使用普通dict,散列也将存储为键)。行的文件位置存储在给定索引处。通过链接解决冲突,即创建一个链表。但是,第一个条目从不包装在节点中(这种优化使代码更复杂,但它节省了相当多的空间)
对于一个有600万行的文件,我选择了哈希表大小为2^24。根据我的测试数据,我得到了933132次碰撞。(一半大小的哈希表在内存消耗上是可比的,但会导致更多的冲突。因为更多的冲突意味着更多的搜索文件访问,所以我宁愿使用大表。)
如果您需要启动许多顺序搜索,那么变体1非常好。由于
set
在内部是一个哈希表,因此它非常擅长搜索。不过,构建它需要时间,而且只有在数据适合RAM的情况下才能正常工作
变体3适用于非常大的文件,因为您有足够的地址空间来映射它们,并且操作系统缓存了足够的数据。你做全面扫描;一旦您的数据停止进入RAM,它就会变得相当慢
如果您需要在行中进行多次搜索,并且无法将数据放入RAM中,那么SQLite绝对是一个不错的主意。将字符串加载到表中,构建索引,然后SQLite为您构建一个漂亮的b树。即使数据不符合要求(有点像@alienhard所建议的),树也可以放入RAM中,即使不符合要求,所需的I/O数量也会大大降低。当然,您需要创建一个基于磁盘的SQLite数据库。我怀疑基于内存的SQLite能否显著击败Variant 1。如果不建立索引文件,您的搜索速度会变慢,而这并不是一项简单的任务。所以最好使用已经开发的软件。最好的方法是使用。你也可以试试
with open('input.txt') as f:
# search_str is matched against each line in turn; returns on the first match:
print search_str in f
使用
search\u str
以正确的换行符序列('\n'
或'\r\n'
)结尾。这应该使用很少的内存,因为文件是逐步读取的。它也应该相当快,因为只读取了文件的一部分。我猜很多路径在DMOZ上都是一样的。
您应该使用a并将单个字符存储在节点上
在保存大型字典或树状数据时,尝试使用O(m)查找时间(其中m是键长度)也可以节省大量空间
您还可以将路径部分存储在节点上以减少节点数-这称为Patricia Trie。但这会使查找速度降低平均字符串le
3) mmap :
(i) data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
(ii) result = data.find(search_str)
4) Hash lookup (using code from @alienhard below):
5) File search (using code from @EOL below):
with open('input.txt') as f:
print search_str in f #search_str ends with the ('\n' or '\r\n') as in the file
6) sqlite (with primary index on url):
from collections import namedtuple
Node = namedtuple('Node', ['pos', 'next'])
def build_table(f, size):
table = [ None ] * size
while True:
pos = f.tell()
line = f.readline()
if not line: break
i = hash(line) % size
if table[i] is None:
table[i] = pos
else:
table[i] = Node(pos, table[i])
return table
def search(string, table, f):
i = hash(string) % len(table)
entry = table[i]
while entry is not None:
pos = entry.pos if isinstance(entry, Node) else entry
f.seek(pos)
if f.readline() == string:
return True
entry = entry.next if isinstance(entry, Node) else None
return False
SIZE = 2**24
with open('data.txt', 'r') as f:
table = build_table(f, SIZE)
print search('Some test string\n', table, f)
Hash table: 128MB (sys.getsizeof([None]*(2**24)))
Nodes: 64MB (sys.getsizeof(Node(None, None)) * 933132)
Pos ints: 138MB (6000000 * 24)
-----------------
TOTAL: 330MB (real memory usage of python process was ~350MB)
with open('input.txt') as f:
# search_str is matched against each line in turn; returns on the first match:
print search_str in f