直接读大文件会爆内存,因Python默认全量加载;mmap通过内存映射按需加载页,避免复制数据,需二进制打开、手动扫描换行符、注意UTF-8跨字符截断及文件截断异常。

为什么直接读大文件会爆内存?
Python 默认用 open().read() 或 readlines() 一次性把整个文件载入内存。几十 GB 的文件,哪怕只是字符串解码或行切分,也会立刻触发 MemoryError 或让系统开始疯狂 swap。这不是 Python 慢,是它根本没打算这么干——你得主动放弃“全量加载”这个直觉。
mmap 是什么,它怎么绕过内存瓶颈?
mmap 不是读文件,而是让操作系统把文件某一段“映射”成进程的虚拟内存地址。你像操作普通字节数组一样访问 mmap 对象(比如 mm[1000:2000]),内核只在真正访问时按需加载对应页(通常 4KB),用完还能自动换出。它不复制数据,也不要求你预估缓冲区大小。
关键点:
-
mmap本身不解析内容——它返回的是memoryview或字节序列,你要自己按格式切片、查找、解码 - 必须用二进制模式打开文件:
open(path, "rb"),否则mmap会报ValueError: mmap length is greater than file size - Windows 下不能对刚创建的空文件做
mmap,要先写入内容或用os.ftruncate()预分配
如何安全地搜索/遍历超大文本文件?
别用 for line in mm.split(b"\n") ——split() 会生成所有行的列表,内存又爆了。正确做法是手动扫描换行符:
立即学习“Python免费学习笔记(深入)”;
import mmap
<p>with open("huge.log", "rb") as f:
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
start = 0
while True:</p><h1>找下一个 \n</h1><pre class="brush:php;toolbar:false;"> end = mm.find(b"\n", start)
if end == -1: # 到文件尾
break
line = mm[start:end] # 注意:不包含 \n
# 处理 line,例如:if b"ERROR" in line: print(line)
start = end + 1注意:
-
mmap.ACCESS_READ是最安全的选项;写入要用ACCESS_WRITE,且文件需可写 - 如果文件含 UTF-8 多字节字符,
line.decode("utf-8")要放在切片后做,避免跨字符截断;更稳的做法是用line.decode("utf-8", errors="ignore") - 不要在循环里反复调用
mm.find()从头找——上面示例已优化为从start开始
处理结构化大文件(如 CSV/二进制)要注意什么?
CSV 没有标准换行保证(字段内可能含 \n),纯靠 find(b"\n") 会错行。这时应改用逐块读取 + 状态机,或借助 csv 模块配合 mmap 的切片能力:
例如定位某个偏移处的记录起始位置,可先用 mm.rfind(b"\n", 0, pos) 往回找上一行尾,再向前找下一个 \n,从而框出完整行。
二进制格式(如自定义 header + record)更适配 mmap:直接按 offset + struct.unpack 解析,比如 struct.unpack("<i mm> 读 4 字节小端整数。</i>
容易被忽略的一点:mmap 映射区域大小固定,若文件在映射后被其他进程截断,后续访问可能触发 ValueError: mmap offset is greater than file size。生产环境建议加 try/except 捕获并重新映射。


















