Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/310.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在大型文本文件中搜索字符串-在python中分析各种方法_Python_Performance_Search_Profiling_Large Files - Fatal编程技术网

在大型文本文件中搜索字符串-在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
  • 使用mmap改进搜索时间的方法。我有一个64位的设置。 [编辑]例如布卢姆过滤器
  • 随着文件大小增加到几GB,是否有任何方法可以继续使用“set”,例如,将其分批拆分 [edit 1]p.S.我需要经常搜索、添加/删除值,并且不能单独使用哈希表,因为我需要稍后检索修改后的值

    欢迎提出任何意见/建议

    [编辑2]更新答案中建议的方法的结果 [编辑3]使用sqlite结果更新


    解决方案:基于所有的评测和反馈,我想我会选择sqlite。第二种选择是方法4。sqlite的一个缺点是,数据库大小是原始csv文件的两倍以上,其中包含URL。这是因为url上的主索引

    那么文本索引解决方案呢

    我会在Java世界中使用Lucene,但是有一个python引擎叫做Whoosh


    使用外部化字符串的自定义哈希表搜索

    要获得快速访问时间和较低的内存消耗,您可以执行以下操作:

    • 对于每一行,计算一个字符串哈希并将其添加到哈希表中,例如,
      index[hash]=position
      (不要存储该字符串)。如果发生冲突,请将该键的所有文件位置存储在列表中
    • 要查找字符串,请计算其哈希并在表中查找。如果找到密钥,请从文件中读取
      位置处的字符串
      ,以验证是否确实匹配。如果有多个位置,请检查每个位置,直到找到匹配或没有
    编辑1:按位置替换行号(正如评论员指出的,显然需要实际位置而不是行号)

    编辑2:为带有自定义哈希表的实现提供代码,这表明此方法比前面提到的其他方法更节省内存:

    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