Python中高效存储访问超大型列表及断点续行方案咨询
解决大文件去重处理+断点续跑的内存问题
你的问题核心在于用列表存储数百万个MD5哈希导致的内存耗尽和查找效率低下——列表的in操作是线性扫描(O(n)时间),数据量上去后不仅慢,还会占用巨量内存,最终触发段错误。下面给你几个简单高效的解决方案,兼顾快速查找和断点续跑:
方案1:用集合+文本文件持久化(最易实现)
集合(set)的成员查询是O(1)时间,比列表快几个数量级,而且内存占用更高效。配合一个文本文件记录已处理的哈希,重启时直接加载到集合就能续跑:
import os # 用来记录已处理哈希的文件 CHECKED_HASHES_PATH = "processed_hashes.txt" checked_hashes = set() # 启动时加载已处理的哈希 if os.path.exists(CHECKED_HASHES_PATH): with open(CHECKED_HASHES_PATH, "r") as f: for line in f: hash_val = line.strip() if hash_val: checked_hashes.add(hash_val) def readline(inFile): print("Opening file: %s\n" % inFile) # 以追加模式打开记录文件,实时写入已处理哈希 with open(inFile, "r") as input_file, open(CHECKED_HASHES_PATH, "a") as record_file: for line in input_file: # 拆分出第一列的MD5哈希 file_hash, _ = line.split("|", 1) file_hash = file_hash.strip() # 清理多余空白/换行符 if file_hash not in checked_hashes: # 执行你的操作 checkit(file_hash) # 更新集合和记录文件 checked_hashes.add(file_hash) record_file.write(f"{file_hash}\n") # 可选:每隔N行手动flush,避免意外断电丢失最新记录 # if len(checked_hashes) % 1000 == 0: # record_file.flush()
为什么这个方案好用:
- 查找快:集合的
in操作是哈希表查询,百万级数据也能瞬间完成 - 内存友好:集合存储的是唯一值,不会重复存储哈希,比列表节省内存
- 断点续跑:重启时从文本文件加载已处理的哈希,直接从上次中断的位置继续(如果需要精确到行的断点,还可以额外记录当前处理到的行号,不过大部分场景下,只要哈希去重,重复处理已完成的哈希也没关系,因为
in checked_hashes会跳过)
方案2:用SQLite数据库(超大数据量首选)
如果你的哈希数量超过千万级,纯文本文件加载到集合可能还是会占用较多内存,这时用SQLite数据库更合适——数据库会把数据存在磁盘上,查询和插入效率依然很高,而且自带主键去重:
import sqlite3 # 数据库文件路径 DB_PATH = "processed_hashes.db" # 初始化数据库连接 conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() # 创建哈希表,主键保证唯一 cursor.execute("CREATE TABLE IF NOT EXISTS processed (hash TEXT PRIMARY KEY)") conn.commit() def is_processed(hash_val): """检查哈希是否已处理""" cursor.execute("SELECT 1 FROM processed WHERE hash = ?", (hash_val,)) return cursor.fetchone() is not None def mark_processed(hash_val): """标记哈希为已处理""" try: cursor.execute("INSERT INTO processed (hash) VALUES (?)", (hash_val,)) conn.commit() except sqlite3.IntegrityError: # 哈希已存在,跳过 pass def readline(inFile): print("Opening file: %s\n" % inFile) with open(inFile, "r") as input_file: for line in input_file: file_hash, _ = line.split("|", 1) file_hash = file_hash.strip() if not is_processed(file_hash): checkit(file_hash) mark_processed(file_hash) # 确保程序结束时关闭数据库连接 try: readline("your_input_file.txt") finally: conn.close()
这个方案的优势:
- 极低内存占用:大部分数据存在磁盘上,内存只需要缓存少量查询结果
- 可靠去重:SQLite的主键约束自动避免重复插入,无需额外判断
- 扩展性强:后续如果需要添加更多元数据(比如处理时间、结果),直接扩展表结构即可
原代码的问题复盘
你之前用列表checkedFile存储哈希,每次if fileHash not in checkedFile都会遍历整个列表,当列表有百万级元素时,每次查询都要花上毫秒级时间,累计下来会拖慢整个程序;而且列表的内存结构(动态数组)比集合(哈希表)更冗余,存储百万级唯一值时会占用更多内存,最终触发段错误。
内容的提问来源于stack exchange,提问作者Bill Swearingen




