用 mmap 可实现大文件按需加载,避免 OOM;需二进制打开、指定 access、及时 close;解析用 struct.unpack_from() 避免全量读取;seek+read 是兼容替代方案;memoryview 不减少内存占用,须谨慎使用。

用 mmap 直接映射大文件,避免一次性读入内存
对几百 MB 甚至 GB 级的二进制数据包(比如传感器原始帧、图像序列、网络 pcap),open().read() 会把全部内容载入 RAM,极易触发 OOM。用 mmap 是最轻量的按需加载方案:它让操作系统用虚拟内存页机制,在真正访问某段数据时才从磁盘加载,且不额外复制缓冲区。
- 必须以二进制模式打开文件:
open(path, "rb"),再传给mmap.mmap() - 注意
mmap对象本身不持有数据,只提供类似bytes的切片接口,但底层是延迟加载的 - Windows 下需指定
access=mmap.ACCESS_READ;Linux/macOS 可省略,但显式声明更安全 - 别忘了调用
.close()或用with上下文管理,否则可能锁住文件或泄漏句柄
import mmap
<p>with open("large.bin", "rb") as f:
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)</p><h1>此时未加载任何数据</h1><pre class="brush:php;toolbar:false;">header = mm[:8] # 仅加载前 8 字节对应页
payload = mm[1024:2048] # 仅加载第 2 个页
mm.close() # 必须关闭用 struct.unpack_from() 解析特定偏移的二进制字段
直接对 mmap 对象或文件对象做 struct.unpack() 会强制读取整块数据,破坏按需加载效果。正确做法是用 struct.unpack_from(),它接受 buffer 和 offset 参数,只触碰目标字节范围。
-
struct.unpack_from(fmt, buffer, offset)中的buffer可以是mmap对象、memoryview或bytes - 确保
offset在文件长度内,否则抛struct.error: unpack_from requires a buffer of at least N bytes - 如果频繁解析同一结构,先用
struct.Struct(fmt).unpack_from()预编译,避免重复解析格式字符串
import struct
<h1>假设每帧 128 字节,前 4 字节是 uint32 时间戳</h1><p>frame_fmt = struct.Struct("<i></i></p><p>with open("data.bin", "rb") as f:
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
ts = frame_fmt.unpack_from(mm, offset=0)[0] # 只加载前 4 字节</p><h1>解析第 100 帧的时间戳(偏移 100 * 128)</h1><pre class="brush:php;toolbar:false;">ts_100 = frame_fmt.unpack_from(mm, offset=100 * 128)[0]
mm.close()
用 io.BytesIO + seek() 模拟随机读取,但慎用于超大文件
当无法使用 mmap(如容器环境禁用内存映射、或需要写入)时,io.BytesIO 不是替代方案——它仍会把全部数据载入内存。真正可行的是基于文件对象的 seek() + read(n) 组合,本质是操作系统层面的随机读。
- 每次
seek()后只read()所需字节数,例如解析某个固定偏移的 header,就f.seek(pos); f.read(16) - 注意 Python 文件对象的缓冲机制:默认带 8KB 缓冲,对小范围读取友好;若读取跨度极大(如跳转 GB 级偏移),可设
buffering=0关闭缓冲,避免预读浪费 - 该方式比
mmap多一次内核态拷贝,性能略低,但兼容性更好
with open("large.bin", "rb", buffering=0) as f:
f.seek(0x1A000) # 跳到 100KB 处
chunk = f.read(32) # 只读 32 字节
# 解析 chunk...
警惕 memoryview 的隐式复制和生命周期陷阱
有人试图用 memoryview(f.read(n)) 实现“局部视图”,这是错误的:f.read(n) 已经把 n 字节全载入内存,memoryview 只是包装它,并未减少内存占用。真正的零拷贝视图只能来自 mmap、bytearray 或支持缓冲协议的对象。
-
memoryview本身不拥有数据,其生命周期依赖底层对象;若底层mmap或bytes被释放,memoryview会变 dangling,访问时报ValueError: memoryview has been detached - 不要对
mmap对象反复创建memoryview,直接用mmap切片更简洁 - 若需多次解析同一块数据,用
memoryview(mm)[start:end]是安全的,只要mm未 close
按需加载的关键不是“看起来没读全”,而是操作系统是否真的没把无关页调入物理内存——这取决于你用的 API 是否绕过用户态缓冲,以及是否触发了 page fault。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











