流式读取核心是“不缓存、不预装、边读边丢”,用原生迭代器(如文件对象、csv.reader、Django .iterator()、ET.iterparse、ijson)处理大数据,避免OOM。

直接用 iterator 实现流式读取,核心是“不缓存、不预装、边读边丢”,让内存只承载当前正在处理的那一小块数据。它不是锦上添花的技巧,而是面对百万级日志、千万行 CSV、GB 级 XML 或数据库全表导出时,防止 OOM 的刚性手段。
文本文件:用文件对象原生迭代器最轻量
Python 文件对象本身就是迭代器,无需额外封装。关键在写法——必须避开 readlines() 和 list(f) 这类全量加载操作:
- ✅ 正确:
with open("data.csv") as f: for line in f:—— 每次只 hold 一行字符串,换行符保留,内存峰值稳定在几 KB - ❌ 危险:
for line in open("data.csv").readlines():—— 先生成含全部行的 list,500MB 文件直接申请 500MB+ 内存 - 编码异常常见于 BOM 或混合编码,建议统一加
encoding="utf-8-sig"自动剥离头部 - 若需跳过空行或注释,直接在循环内判断:
if not line.strip() or line.startswith("#"): continue
结构化数据:csv + iterator 组合防解析膨胀
别自己 line.split(","),字段含逗号或换行会崩。用 csv.reader 流式解析,它内部也基于文件迭代器:
-
import csvwith open("huge.csv", encoding="utf-8") as f:reader = csv.reader(f)for row in reader:process(row) # row 是 list,但仅当前行,不缓存历史 - 如只需某几列,用
csv.DictReader(f, fieldnames=["id","name"])配合字段白名单,避免构建冗余 key - 注意:不要对
reader调用list()或len(),会触发全量读取
Django ORM:用 .iterator() 切断 QuerySet 缓存链
Django 默认把 QuerySet 所有结果缓存在 _result_cache 中,哪怕你只 for 一次。解决方式极简但必须严格遵守调用时机:
- ✅ 有效:
for obj in MyModel.objects.filter(status=1).iterator(chunk_size=1000):—— 禁用缓存,每批拉 1000 行,处理完即释放引用 - ❌ 无效:
qs = MyModel.objects.all(); qs.iterator(); for obj in qs:——.iterator()必须在最终执行前调用,放错位置等于没写 - 如果只取 ID 或单字段,叠加
values_list("id", flat=True).iterator(),跳过模型实例化,省内存 5–10 倍 - 慎用
select_related和prefetch_related:它们虽解 N+1,却把关联对象也载入内存,大结果集下可能翻倍占用
超大 XML/JSON:用 iterparse 或 ijson 流式抽取关键节点
DOM 解析(ET.parse)会把整棵树塞进内存;SAX 又太底层。推荐标准库 iterparse —— 事件驱动 + 显式清理:
- 监听
"start"和"end"事件,只在end时处理目标标签(如"record") - 处理完立即调用
elem.clear()和root.clear(),切断子元素引用,让 GC 及时回收 - 示例片段:
context = ET.iterparse(f, events=("start", "end")),遍历context并按需构建轻量 dict - JSON 场景可用
ijson.parse()或ijson.items(f, "records.item"),逐个 yield 解析后的 Python 对象,不驻留全文本
二进制或无结构大文件:手动分块 + partial + iter 控制粒度
当“按行”不适用(如固定长度记录、日志无换行、base64 blob),就退回到字节块控制:
- 用
functools.partial(f.read, 8192)构造无参读取函数,再套iter(..., b"")形成安全迭代器 - 块大小选 8KB–64KB:太小增加系统调用开销,太大削弱内存优势;实测 32KB 在多数 SSD 上吞吐与内存平衡最佳
- 注意边界问题:文本内容被切在两块中间时,需自行缓冲拼接;二进制场景则无需关心
- 不推荐 while + f.read(n) 手写循环——容易漏判空返回、异常时句柄未关,
iter + partial更健壮


















