Python 内存映射文件处理超大文件
当文件体积超过可用内存(例如几十 GB 的日志、科学数据或视频文件),常规的 open().read() 方式会直接导致程序崩溃。Python 的 mmap 模块提供了一种“内存映射”机制,让你像操作内存一样读写超大文件,而无需一次性加载全部内容。
核心原理:什么是内存映射?
内存映射(Memory Mapping)是一种操作系统功能,它将磁盘上的文件“链接”到进程的虚拟内存地址空间。你对这段内存的读写操作,会由操作系统自动同步到磁盘文件——无需手动调用 read() 或 write()。
- 优点:访问速度接近内存操作,且只加载当前需要的部分(按需分页)。
- 注意:修改映射内容会直接影响原文件(除非以只读模式打开)。
基础操作:读取超大文件
假设你有一个 50GB 的文本日志文件 huge.log,需要查找其中包含 "ERROR" 的行。
-
导入模块并打开文件
使用open()以二进制模式('rb')打开文件,这是mmap的要求:import mmap with open('huge.log', 'rb') as f: # 创建内存映射对象 with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as mm: # 后续操作在此缩进内进行 -
按行扫描文件
遍历映射对象的每一行(注意:mmap对象支持类似文件对象的迭代):for line in iter(mm.readline, b""): if b"ERROR" in line: print(line.decode('utf-8').strip())iter(mm.readline, b"")表示持续调用mm.readline()直到返回空字节(文件结束)。- 因为文件以二进制打开,
line是bytes类型,需用.decode()转为字符串。
-
高效搜索特定位置
跳转到文件末尾前 1MB 处检查最后几行(避免从头扫描):mm.seek(0, 2) # 移动到文件末尾 file_size = mm.tell() start_pos = max(0, file_size - 1024*1024) # 1MB mm.seek(start_pos) # 跳过第一行残缺内容(因从中间开始) mm.readline() for line in iter(mm.readline, b""): print(line.decode('utf-8').strip())
修改超大文件内容
要编辑文件(如替换文本),必须以读写模式打开。
-
创建可写映射
确保文件以'r+b'模式打开('w'模式会清空文件):with open('huge.log', 'r+b') as f: with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_WRITE) as mm: # 修改操作 -
替换固定长度的内容
定位到目标位置并覆盖等长字节(内存映射不支持直接插入/删除):# 将第 1000 字节开始的 5 字节替换为 "FIXED" mm[1000:1005] = b"FIXED"- 替换内容长度必须与原内容严格一致,否则会破坏文件结构。
-
追加内容到文件末尾
扩展文件大小后再写入(先调整文件尺寸):original_size = mm.size() new_content = b"\nAppended log entry" mm.resize(original_size + len(new_content)) # 扩展映射和文件 mm[original_size:] = new_content # 写入新内容
关键参数详解
创建 mmap 对象时,mmap.mmap() 的核心参数如下:
| 参数 | 说明 | 常用值 |
|---|---|---|
fileno |
文件描述符 | f.fileno()(来自已打开的文件对象) |
length |
映射区域长度(字节) | 0 表示映射整个文件 |
access |
访问权限 | mmap.ACCESS_READ(只读)<br>mmap.ACCESS_WRITE(可写)<br>mmap.ACCESS_COPY(写时复制) |
length=0是最常用设置:自动映射整个文件,无需手动计算大小。ACCESS_COPY模式:修改内容不会影响原文件,适用于临时分析场景。
高级技巧:处理结构化二进制数据
对于包含固定格式记录的二进制文件(如传感器数据),可结合 struct 模块解析。
-
定义数据结构
假设每条记录占 12 字节:4 字节整数 ID + 8 字节浮点数温度值。import struct RECORD_SIZE = 12 record_format = "<if" # 小端序:1个int + 1个float(实际需8字节对齐,此处简化) -
随机访问记录
直接跳转到第 N 条记录位置并解析:with open('sensor.dat', 'rb') as f: with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm: # 读取第 100 条记录 offset = 100 * RECORD_SIZE data = mm[offset:offset + RECORD_SIZE] sensor_id, temperature = struct.unpack("<if", data) print(f"ID: {sensor_id}, Temp: {temperature}")
注意事项与陷阱
- 文件锁问题:在 Windows 上,被映射的文件无法被其他进程删除或重命名,直到映射关闭。
- 内存释放:务必使用
with语句或显式调用mm.close(),否则可能占用虚拟内存。 - 大端/小端序:处理跨平台二进制数据时,在
struct.unpack()中明确指定字节序(如"<"小端,">"大端)。 - 稀疏文件支持:内存映射能高效处理稀疏文件(含大量空洞的文件),操作系统仅加载非空部分。
性能对比:何时使用 mmap?
| 场景 | 推荐方案 |
|---|---|
| 顺序读取整个小文件(<1GB) | open().readlines() |
| 随机访问超大文件(>10GB) | mmap |
| 频繁修改文件中部内容 | mmap(固定长度替换) |
| 追加日志到文件末尾 | open() 以 'a' 模式 |
测试建议:对目标文件大小和访问模式做基准测试。mmap 在随机访问大文件时优势明显,但小文件可能因系统调用开销反而更慢。

暂无评论,快来抢沙发吧!