应使用 blake2b 哈希+二进制预处理实现低内存去重:以 'rb' 模式读取,统一裁剪行尾换行符,生成 16 字节 raw bytes 哈希存入 set;超千万行时改用分块写临时二进制文件,再调用系统 sort -u 合并去重。

直接用 hashlib 对每行做哈希再存进 set,内存会爆——百万级文本行的原始字符串 + 哈希对象叠加,轻松吃掉几 GB;真要稳定去重,得绕开全量加载和重复哈希计算。
为什么不能直接 set(line for line in f) 或 set(hashlib.md5(line.encode()).hexdigest() for line in f)
前者比对的是原始字符串,换行符、空格、BOM、编码隐式转换都会导致“相同语义的行”被当成不同行;后者看似稳妥,但 hexdigest() 返回 32 字节字符串,比原行还长,set 存储开销翻倍。更关键的是:一旦文件超 500 万行,Python 进程常因内存碎片或 OS OOM killer 被干掉。
- Windows 上默认
open()是 CRLF 换行,Linux 是 LF,同一文件在不同系统读出来哈希值不同 -
line.strip()看似合理,但会误删有意义的首尾空格(比如日志中对齐字段) - 用
hashlib.sha256()比md5()更安全,但哈希计算本身不瓶颈,瓶颈在对象创建和 set 插入
用 hashlib.blake2b() + 二进制预处理做低内存哈希
blake2b 比 md5 快 20%–30%,且原生支持 digest_size=16 输出 16 字节 bytes,可直接塞进 set,比 hex 字符串省 2 倍空间。关键是要统一换行和编码处理逻辑,且避免反复 encode():
import hashlib
def line_hash(line: bytes) -> bytes:
# 直接接收 bytes,跳过 decode/encode 开销
# 去掉末尾 \n\r,但保留前面所有空白
if line.endswith(b'\r\n'):
line = line[:-2]
elif line.endswith(b'\n') or line.endswith(b'\r'):
line = line[:-1]
return hashlib.blake2b(line, digest_size=16).digest()
<p>seen = set()
with open('data.txt', 'rb') as f: # 强制 binary mode
for line in f:
h = line_hash(line)
if h not in seen:
seen.add(h)
print(line.rstrip(b'\r\n'), file=output_f)
- 必须用
open(..., 'rb'),否则line是 str,每次循环都隐式 encode,CPU 白烧 -
line_hash中只裁剪行尾换行符,不碰内容——这是和业务约定的前提:换行是分隔符,不是数据 - 如果原始数据含 NULL 字节(
b'\x00'),blake2b仍能正常哈希,但需确认下游是否接受
超过千万行时必须流式写入 + 分块去重
内存里存不下全部哈希?那就别存。用 itertools.islice 分批读,每批 10 万行,哈希后写入临时文件,最后用 sort -u(Linux/macOS)或 PowerShell 的 Sort-Object -Unique(Windows)合并去重:
立即学习“Python免费学习笔记(深入)”;
from itertools import islice
import subprocess
<p>def process_chunk(lines_iter, chunk_id):
tmp_path = f'tmp<em>hash</em>{chunk_id}.bin'
with open(tmp_path, 'wb') as out:
for line in lines_iter:
h = line_hash(line)
out.write(h) # 写 raw bytes,非文本
return tmp_path</p><h1>主流程</h1><p>chunks = []
with open('big.txt', 'rb') as f:
while True:
chunk = list(islice(f, 100000))
if not chunk:
break
chunks.append(process_chunk(chunk, len(chunks)))</p><h1>合并所有 tmp<em>hash</em>*.bin 并去重(外部命令更快)</h1><p>all_files = ' '.join(chunks)
subprocess.run(f'sort -u {all_files} | wc -l', shell=True)
- 写
.bin文件比写文本快,且避免编码问题;后续可用xxd或 Pythonhashlib验证哈希一致性 - 不要在 Python 里用
set合并多个大文件——磁盘 IO 和内存 swap 会让你等一小时 - 若必须纯 Python 解决,改用
sqlite3内存数据库建CREATE TABLE hashes(h BLOB PRIMARY KEY),逐批INSERT OR IGNORE
真正卡住的往往不是哈希算法选型,而是没意识到:行末换行符规范、文件打开模式、以及“去重”到底指字节一致还是语义一致——这三者错一个,结果就不可复现。


















