Python mmap 实战:超大二进制文件原地修改指定偏移字节

举报
yd_250998817 发表于 2026/10/22 19:23:36 2026/10/22
【摘要】 处理超大二进制文件时,若只需修改指定偏移的少量字节,用 read() 读入整个文件会消耗大量内存。本文介绍 Python mmap 模块的内存映射方式,给出可运行的最小示例,实现原地修改单字节,并讨论跨平台注意事项、运行方法及边界限制。

Python mmap 实战:超大二进制文件原地修改指定偏移字节

问题场景

处理磁盘镜像、数据库文件、大型日志或固件包时,经常需要修改文件中某个固定偏移处的少量字节。例如,修正一个标志位、替换一个校验和、或修改配置字节。如果文件达到数 GB 甚至更大,用 f.read() 将整个文件读入内存再修改,会占用巨量内存,甚至导致程序因 OOM 崩溃。而我们真正关心的可能只是其中 1 个字节。

Python 标准库中的 mmap 模块提供了内存映射文件的能力。它把文件映射到进程的虚拟地址空间,操作系统按页加载数据,修改时也只影响相关页面,并由系统在适当时机写回文件。这样既避免了显式读取整个文件,又能像操作字节数组一样直接修改文件内容。

最小可运行示例

下面是一个完整的 Python 脚本,实现“原地修改指定偏移的单字节”。它使用 mmap.ACCESS_WRITE 模式,保证修改会写回底层文件,并且不改变文件大小。

import mmap
import os


def patch_byte(file_path: str, offset: int, value: int) -> int:
    """
    将 file_path 中偏移 offset 处的字节修改为 value(0-255)。
    返回修改前的字节值。
    文件大小不变。
    """
    if not 0 <= value <= 255:
        raise ValueError('value 必须在 0 到 255 之间')

    with open(file_path, 'r+b') as f:
        # 获取文件大小
        f.seek(0, os.SEEK_END)
        file_size = f.tell()

        if not 0 <= offset < file_size:
            raise ValueError(f'offset 必须在 [0, {file_size}) 范围内')

        # length=0 表示映射整个文件。
        # 注意:空文件会触发 ValueError/OSError,所以前面已做范围检查。
        with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_WRITE) as mm:
            old = mm[offset]
            mm[offset] = value          # 像字节数组一样赋值
            mm.flush()                  # 显式刷新到磁盘
            return old


if __name__ == '__main__':
    # 创建一个测试文件(占位示例)
    test_path = 'test_patch.bin'
    with open(test_path, 'wb') as f:
        f.write(b'ABCDEFGHIJ')          # 10 字节

    old = patch_byte(test_path, 3, ord('X'))
    print(f'偏移 3 的原值: {chr(old)}')

    with open(test_path, 'rb') as f:
        print('修改后内容:', f.read())

    os.remove(test_path)

运行方法

  1. 将上述代码保存为 patch_byte.py。
  2. 在终端执行 python patch_byte.py。
  3. 预期输出:
偏移 3 的原值: D
修改后内容: b'ABCXEFGHIJ'

脚本会先创建一个小文件 test_patch.bin,内容为 ABCDEFGHIJ。然后调用 patch_byte 把偏移 3 处的 D 改成 X,最后读取文件确认结果并删除测试文件。

在实际使用时,只需把 test_path 换成你的目标文件路径,把 offset 和 value 换成实际需要的值即可。

关键点说明

  • 打开模式:使用 r+b 以读写方式打开文件,这是跨平台最稳妥的做法。在 Windows 上,如果 access 不是 ACCESS_READ,文件必须支持写入。
  • 访问模式:mmap.ACCESS_WRITE 表示写入会同步到文件。如果只想在内存中修改而不影响原文件,可以使用 mmap.ACCESS_COPY。
  • 映射长度:length=0 表示映射整个文件。对于超大文件,这并不会立即把文件全部读入内存,而是建立虚拟内存映射,按需分页。
  • 修改方式:mm[offset] = value 直接修改单个字节;也可以用切片批量修改,例如 mm[offset:offset+4] = b'\x00\x01\x02\x03',但切片长度必须匹配。
  • 刷新:调用 mm.flush() 可以把修改强制写回磁盘。即使不调用,关闭映射时通常也会刷新,但显式调用更可控。

边界与限制

  1. 文件不能为空:空文件无法映射,mmap.mmap 会抛出 ValueError(Unix)或 OSError(Windows)。上面的代码在映射前检查了偏移范围,空文件时 file_size 为 0,任何偏移都会越界,从而提前报错。
  2. 偏移必须在文件内:合法范围是 [0, file_size - 1]。不能等于 file_size,因为那是追加位置,而 mmap 不能直接扩展文件。
  3. 不能改变文件大小:mmap 映射的是已有文件区域,无法通过它插入或删除字节。如果需要扩展文件,应先用其他方式调整文件大小,再重新映射。
  4. 并发访问:如果多个进程或线程同时修改同一区域,必须使用文件锁或进程间锁进行同步,否则可能覆盖彼此的修改。
  5. 跨平台差异:Unix 和 Windows 对 mmap 的实现细节不同。例如,映射偏移通常要求页对齐;如果只映射文件的一部分,需要关注 mmap.ALLOCATIONGRANULARITY 或 mmap.PAGESIZE。本文示例映射整个文件,避开了对齐问题。
  6. 超大文件与 32 位系统:在 32 位进程中,虚拟地址空间有限,映射整个超大文件可能失败。此时可以只映射包含目标偏移的一小段区域,通过 offset 参数指定映射起点,并注意页对齐。
  7. 网络文件系统:在 NFS 等网络文件系统上,mmap 的写入可见性和一致性可能不如本地文件系统可靠,建议在本地磁盘上操作。
  8. 权限:修改文件需要相应的写权限,否则打开文件或映射时会失败。

小结

对于超大二进制文件中少量字节的原地修改,mmap 提供了一种内存友好且直观的方式。本文给出的示例代码可以直接运行,也可以作为工具函数嵌入更大的脚本中。使用时注意偏移范围、文件非空、并发同步和跨平台对齐要求,就能安全地完成修改任务。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。