不能用 random.sample() 直接读大文件,因其需将全部数据载入内存成列表,易导致 OOM;正确解法是流式抽样——用蓄水池算法边读边随机保留 k 行,确保每行被选概率严格为 k/N。

为什么不能用 random.sample() 直接读文件?
因为 random.sample() 要求传入一个可迭代对象(比如列表),而超大文本文件(比如 50GB 的日志)根本没法一次性读进内存转成列表。强行 list(f) 会直接 OOM,连进程都起不来。
真正的解法是「流式抽样」——边读边决定要不要保留某一行,且保证每行被选中的概率严格相等。核心就靠 reservoir sampling(蓄水池算法)。
用蓄水池算法实现固定大小随机抽样
假设你要从未知行数的文件中抽 k=1000 行,且每行被选中概率均为 k / N(N 是总行数)。算法逻辑极简:
- 前
k行无条件放进结果列表 - 从第
k+1行开始,对第i行(索引从 1 开始),以概率k / i决定是否替换结果列表中某个随机位置
Python 实现只需 10 行左右:
立即学习“Python免费学习笔记(深入)”;
import random
<p>def reservoir_sample(filepath, k):
sample = []
with open(filepath, 'r', encoding='utf-8') as f:
for i, line in enumerate(f, 1):
if i <= k:
sample.append(line.rstrip('\n'))
else:
j = random.randint(1, i)
if j <= k:
sample[j - 1] = line.rstrip('\n')
return sample</p><h1>用法</h1><p>my_sample = reservoir_sample('huge.log', k=1000)
注意:random.randint(1, i) 必须是闭区间,否则第 i 行被选中概率会变成 (k-1)/i,破坏均匀性。
遇到空行、编码错误或大字段怎么办?
真实日志/CSV 常含异常:BOM 头、utf-8 解码失败、超长行卡住迭代器。别让单行错误中断整个抽样:
- 加
encoding='utf-8-sig'自动跳过 BOM - 用
try/except UnicodeDecodeError跳过坏行(不推荐静默吞掉,至少print(f"skip line {i}: decode error")) - 限制单行长度:
for line in f: if len(line) > 10_000: continue,防内存爆涨
如果文件是 CSV 格式且字段含换行符,line 迭代会错切——这时必须用 csv.reader 流式解析,但要注意它内部仍会缓存整行,需配合 csv.field_size_limit() 调大上限。
想按比例抽样(比如 1%)而不是固定数量?
直接用 random.random() 最简单,但要注意:这只能保证「期望比例」是 1%,实际抽到的行数是服从二项分布的(比如 100 万行可能抽到 9923 或 10087 行)。如果业务强依赖精确数量,还是得用蓄水池;如果只是做统计探查,按比例抽更快、更省内存:
def proportional_sample(filepath, ratio=0.01):
sample = []
with open(filepath, 'r', encoding='utf-8-sig') as f:
for line in f:
if random.random() < ratio:
sample.append(line.rstrip('\n'))
# 可选:设个硬上限避免意外抽太多
if len(sample) >= 10000:
break
return sample
蓄水池算法本身不依赖文件总行数,但它的时间复杂度是 O(N),IO 是瓶颈;而比例抽样平均只读 O(ratio × N) 行,实测在 ratio
真正容易被忽略的是:别在抽样后立刻用 pandas.read_csv(StringIO('\n'.join(sample))) ——拼接字符串再解析,既慢又可能因换行符污染出错。应该把抽到的行写入临时文件,再用 pandas.read_csv(tempfile) 安全加载。


















