【问题标题】:How to store (and access) a very large list in python如何在 python 中存储(和访问)一个非常大的列表
【发布时间】:2018-06-29 14:37:07
【问题描述】:

我有一个包含超过 400 万行的文件,我想逐行读取,并对数据的第一个“列”执行操作。可能有重复的数据,我想确保我只执行一次这些操作,并且能够重新启动程序并从它停止的地方开始。

我的解决方案是解析该行,将值存储到一个列表中,然后“如果 x 不在列表中”执行操作,然后如果/当程序死亡/停止/被杀死时将该列表存储为泡菜。

但是脚本在运行几个小时后会出现段错误,我认为这是因为我在 checkFile 列表中存储了大量 MD5 和而烧毁了我的所有 ram。

因为文件太大(超过 400 万个条目),我需要一些快速查找的东西,希望有一个简单的解决方案。谢谢!

数据如下所示:6e5f6c90e6bf31c31283afb8dca93da8|mouse.gif|10102017

我当前的代码如下所示:

checkedFile = []

def readline(inFile):
print("Opening file: %s\n" % inFile)
try:
    with open(inFile, "r") as inputFile:
        for line in inputFile: # read in file line by line
            fileHash, garbage = line.split("|",1) 
            if fileHash not in checkedFile:
                checkedFile.append(fileHash)
                checkit(fileHash) # 

【问题讨论】:

  • checkit 是做什么的?如果您的文件大约 4MB,那么它应该没有任何问题... => python 中的列表和字符串只有 1 个限制,据我回忆,这是 RAM 大小。我希望你的 ram 更像是在 GB 范围内:-)
  • 文件读取是作为迭代完成的,因此不太可能导致内存问题。所以要么是checkedFile 变得太大,要么是checkit 导致了这个问题(不管那是什么?)
  • 它接受 fileHash 并对返回 json 的本地运行的 web 服务进行 REST 查找。我的假设是“checkedFile”变得越来越大。
  • “单一职责”的一个好做法是首先将完整的 inFile 加载到 checkedFile 中。之后遍历checkedFile并对每个元素进行checkit。在这种情况下,您知道 2 中的哪一个失败了。您能否也在这里获取完整的错误信息?
  • 至少有两个问题:分段错误和你的checkedFile的低效处理。列表中的成员资格测试非常低效。将其替换为checkedFile = set()(制作一组)并将checkedFile.append(fileHash) 替换为checkedFile.add(fileHash)。这样至少你不会在簿记上浪费很多时间。

标签: python


【解决方案1】:

我想您遇到的问题之一是您选择的数据结构。由于checkedFile 是一个列表,因此检查一个哈希是否在列表中有O(N) time complexity。我的第一个建议是使用 python set。这使用了性能为O(1) 的哈希映射。

checkedFile = set()

def readline(inFile):
    print("Opening file: %s\n" % inFile)
    with open(inFile, "r") as inputFile:
        for line in inputFile: # read in file line by line
            fileHash, garbage = line.split("|",1) 
            if fileHash not in checkedFile:
                checkedFile.add(fileHash)
                checkit(fileHash)

Python 只为2 reasons 提供真正的段错误,因此您肯定会耗尽内存。我发现您使用列表存储进行分段错误很奇怪,因为每个哈希是 16 字节 * 400 万 = 64Mb,我认为这与您的典型计算机中的 ram 数量相差甚远。

我建议采用持久存储路线。这种方法不会耗尽内存,因为它会存储在您的硬盘上。

 import sqlite3
 connection = sqlite3.connect('cache.db')
 CREATE_CACHE_TABLE = '''CREATE TABLE IF NOT EXISTS cache ( 
                         hash TEXT PRIMARY KEY
               )'''
 SELECT_STATEMENT = 'SELECT hash FROM cache WHERE hash = ?'
 INSERT_STATEMENT = 'INSERT INTO cache VALUES ?'

 with connection:
     connection.execute(CREATE_CACHE_TABLE)
 with open(inFile, "r") as inputFile:
      for line in inputFile:
          fileHash, garbage = line.split("|", 1)
          with connection:
               if not connection.execute(SELECT_STATMENT, (fileHash,)).fetchone():
                    connection.execute(INSERT_STATEMENT, (fileHash,))
                    checkit(fileHash)

作为额外的奖励,您还可以缓存来自checkit 的结果,以便在由于某种原因停止计算时可以继续计算。这也将使您受益,因为 Web 请求将需要10ms 的最短响应时间。您提到 Web 请求返回一个 json 响应。我在sqlite with python 如何在sqlite 中存储json 之前写过这个。您需要修改函数checkit 以返回json 数据。

import sqlite3
import json

def adapt_json(data):
    return (json.dumps(data, sort_keys=True)).encode()

def convert_json(blob):
    return json.loads(blob.decode())

sqlite3.register_adapter(dict, adapt_json)
sqlite3.register_converter('JSON', convert_json)

connection = sqlite3.connect('cache.db', detect_types=sqlite3.PARSE_DECLTYPES)
CREATE_CACHE_TABLE = '''CREATE TABLE IF NOT EXISTS cache (
                         hash TEXT PRIMARY KEY,
                         check_results JSON
               )'''
SELECT_STATEMENT = 'SELECT hash FROM cache WHERE hash = ?'
INSERT_STATEMENT = 'INSERT INTO cache VALUES ?, ?'

with connection:
    connection.execute(CREATE_CACHE_TABLE)
with open(inFile, "r") as inputFile:
    for line in inputFile:
        fileHash, garbage = line.split("|", 1)
        with connection:
            if not connection.execute(SELECT_STATMENT, (fileHash,)).fetchone():
                json_data = checkit(fileHash)
                connection.execute(INSERT_STATEMENT, (fileHash, json_data))

如果您使用最后一种方法。您可以与文件的不同块并行运行此程序,它仍然可以工作。如果你最终得到一个更大的文件,这种方法应该可以很好地扩展。

【讨论】:

  • 嘿,感谢您花这么多时间来解释这一点。我非常感谢您的回复,这非常有帮助,并将以这种方式实施。我总是在这个网站上学到很多东西!
  • 没问题!很抱歉,持久存储路由方法更复杂。可能有一个更简单的解决方案,但这是我所知道的最简单和最高效的选项。您确实获得了好处,您可以使用文件的不同部分并行运行该程序多次。我想你的瓶颈是网络请求。 10 毫秒 * 400 万 = 大约 11 小时。
  • “一个网络请求将需要至少 10 毫秒的响应时间”?你是从哪里得到这个想法的?
  • 这是一个很好的参考techempower.com/benchmarks。对于 python 来说,这是正确的,但 1-2 毫秒是所有语言的绝对最小值。
猜你喜欢
  • 2011-07-21
  • 2021-09-19
  • 2017-12-05
  • 1970-01-01
  • 1970-01-01
  • 2017-04-08
  • 2019-08-07
  • 2015-06-30
  • 1970-01-01
相关资源
最近更新 更多