当前位置:首页 > 文章列表 > 文章 > python教程 > Python mmap 怎样分段处理超过内存的大文件

Python mmap 怎样分段处理超过内存的大文件

来源:17golang原创 2026-10-09 07:49:12 0浏览 收藏

用 Python mmap 处理超过物理内存的大文件时,不要把“映射整个文件”和“把整个文件读入 Python 堆”混为一谈。映射主要占用虚拟地址空间,文件页由操作系统按需调入;但在 32 位进程、地址空间紧张或文件特别大时,整文件映射仍可能失败。更稳妥的做法是:每次只映射一个固定大小的窗口,映射起点按 mmap.ALLOCATIONGRANULARITY 对齐,再把逻辑窗口交给解析器。

如果记录可能跨窗口,还要保留窗口末尾的未完成字节,并与下一段开头拼接。下面给出的实现以换行分隔记录为例,每轮只保留当前映射和一条未完成记录,不会把完整文件复制到内存。

官方文档:https://docs.python.org/3/library/mmap.html

分段 mmap 实现方案
  1. 先取得文件真实大小,空文件直接结束。
  2. 用逻辑偏移计算对齐偏移和页内差值。
  3. 映射“页内差值 + 当前窗口长度”,但只解析逻辑窗口。
  4. 用 find 在限定范围内找分隔符,避免复制整个窗口。
  5. 用 carry 保存跨窗尾部,并设置最大记录长度。
  6. 每段使用上下文管理器关闭,提前停止时显式关闭生成器。

为什么不能只写 mmap(fileno, 0)

在 Unix 上,length=0 表示映射调用时的当前文件大小;Windows 也支持用 0 表示当前大小,但空文件不能建立映射。对 64 位进程中的普通大文件,整文件映射往往可行,而且不等于立刻占用同等大小的物理内存。

问题在于,映射仍要保留连续虚拟地址范围。几十 GB、数百 GB 文件,或同时存在多个大映射时,整文件方案可能碰到地址空间、平台限制或资源管理难题。分段映射把最大映射范围固定在一个可控窗口内,适合批量日志、CSV、JSON Lines 和变长二进制记录。

最小配方:先对齐,再限定逻辑窗口

mmap 的 offset 不能随意取值。Python 官方文档要求它必须是 ALLOCATIONGRANULARITY 的倍数;Unix 上这个值等于页大小,Windows 上通常是系统分配粒度。业务窗口的逻辑起点不一定对齐,因此需要向下取整:

import mmap

logical_offset = 70_000_000
window_size = 64 * 1024 * 1024
granularity = mmap.ALLOCATIONGRANULARITY

# 映射起点向下对齐到系统要求的粒度
aligned_offset = logical_offset - (logical_offset % granularity)

# delta 表示逻辑窗口在本次映射中的起始位置
delta = logical_offset - aligned_offset
mapping_length = delta + window_size

真正需要处理的是 [delta, delta + window_size),不是整个映射区。最后一个窗口还要用剩余文件长度收窄,保证 aligned_offset + mapping_length 不超过文件末尾。

Python mmap 源文件、对齐偏移、逻辑窗口与跨窗记录缓存的静态关系图
图1:对齐映射、逻辑窗口与记录拼接的静态关系。该图是结构说明图,不是运行截图。

跨窗口记录怎么拼起来

如果每条记录固定长度,窗口按记录宽度对齐即可。换行文本更常见的情况是:窗口 A 的末尾只包含一条记录的前半部分,窗口 B 才出现换行符。直接逐段调用 splitlines() 会把这条记录错误拆成两条,而且会为整个窗口创建大量新 bytes 对象。

更节省的做法是用 mmap.find(b"\n", start, end) 在逻辑窗口内定位换行符。每找到一条完整记录就立即交给消费者,最后未匹配的尾部存入 carry。进入下个窗口后,只在第一条完整记录前拼接 carry。

carry 的大小必须有上限。否则一个损坏文件里数 GB 都没有换行符,程序仍会不断扩张 Python 堆,失去分段处理的意义。上限应按业务协议设置,例如日志单行允许 8 MiB,超过就报告异常数据。

完整片段:逐段产出字节行

下面的生成器返回不含换行符的 bytes。它使用 ACCESS_READ 保持只读语义,按系统粒度对齐 offset,并用上下文管理器在每个窗口完成后立即关闭映射。

from __future__ import annotations

import mmap
import os
from collections.abc import Iterator


def iter_mmap_lines(
    path: str | os.PathLike[str],
    *,
    window_size: int = 64 * 1024 * 1024,
    max_record_size: int = 8 * 1024 * 1024,
) -> Iterator[bytes]:
    """按窗口读取换行分隔记录,返回不含换行符的字节串。"""
    if window_size  max_record_size:
                        # 防止异常超长记录持续扩大 Python 堆
                        raise ValueError("记录长度超过 max_record_size")

                    yield record
                    carry = b""
                    cursor = newline + 1

                # 只复制当前窗口最后一条未完成记录
                tail = region[cursor:window_end]
                if len(carry) + len(tail) > max_record_size:
                    raise ValueError("跨窗口记录长度超过 max_record_size")
                carry += tail

            logical_offset += payload_size

        if carry:
            # 文件末尾没有换行符时,仍产出最后一条记录
            yield carry

这里没有使用 memoryview 跨过 with mmap.mmap(...) 的边界。若活动视图仍引用映射,关闭 mmap 可能失败;直接在窗口内切出当前记录的 bytes,生命周期更清晰。代价是每条完整记录会复制一次,这通常正是下游解析所需要的独立对象。

如何消费并在提前停止时释放资源

普通 for 循环读到结尾时,生成器会自然退出,当前 mmap 和文件都会关闭。如果消费者可能提前 break,可用 contextlib.closing 保证生成器收到 close(),从而立即展开内部上下文管理器:

from contextlib import closing

with closing(iter_mmap_lines("events.log")) as records:
    for raw_record in records:
        # 完整记录形成后再解码,避免拆断 UTF-8 多字节字符
        text = raw_record.decode("utf-8")
        if "FATAL" in text:
            print(text)
            # 提前停止时 closing 会关闭生成器及当前映射
            break

如果是 CSV 或 JSON Lines,建议先在字节层完成跨窗拼接,再进行解码与结构化解析。这样 UTF-8 多字节字符即使落在窗口边缘,也会随整条记录一起进入解码器。

变体:什么时候映射整个文件更简单

当文件规模可控、运行在 64 位进程、需要大量随机定位,而且文件在映射期间保持稳定时,整文件映射通常更简洁。此时可使用 length=0,通过 find 或 readline 扫描,但仍要先排除空文件,并用上下文管理器关闭。

import mmap
import os

with open("index.bin", "rb") as source:
    size = os.fstat(source.fileno()).st_size
    if size:
        # length=0 表示映射当前完整文件;只读访问禁止误写
        with mmap.mmap(source.fileno(), 0, access=mmap.ACCESS_READ) as region:
            marker = region.find(b"INDEX-V1")
            print(marker)

若任务只是从头到尾读取一次,普通带缓冲文件迭代器也可能更快、更简单。是否使用 mmap 应在真实文件大小、存储设备和访问模式下测量,不要仅凭“零拷贝”字样下结论。

兼容坑:空文件、粒度和生命周期

Python mmap 文件描述符、映射粒度、访问模式和资源状态的静态关系图
图2:mmap 构造参数、平台约束与资源状态的静态关系。该图不表示真实运行结果。
问题安全处理
空文件先用 fstat 判断大小;Windows 不允许空映射。
offset 未对齐向下对齐到 ALLOCATIONGRANULARITY,用 delta 恢复逻辑起点。
误修改源文件读取任务显式使用 ACCESS_READ,写入会抛 TypeError。
映射关闭后继续访问不要把 region 或它的活动视图带出 with;关闭后方法会抛 ValueError。
处理期间文件被截断让上游先写临时文件再原子替换,或建立外部协调,避免原文件尺寸变化。
WASI 环境官方文档标明 mmap 不可用,改用普通分块读取。
写映射需要落盘另行使用合适访问模式并调用 flush();范围 offset 也要满足页粒度要求。

常见问题

mmap 会不会把整个文件都占进内存?

不会像 read() 那样立即返回同等大小的 bytes。它建立虚拟内存映射,页面按访问情况进入物理内存;但映射范围仍占虚拟地址空间,因此极大文件适合分段。

窗口越大越快吗?

不一定。大窗口减少映射次数,小窗口降低地址空间占用并缩短单段生命周期。应按存储设备、记录分布、并发任务和解析成本实测。

为什么不直接对每段调用 splitlines?

splitlines() 会为整段产生许多对象,而且会把跨窗记录拆错。用 find 限定搜索区间,再只复制完整记录和尾部残片,更容易控制内存。

文件被另一个进程追加可以继续读吗?

当前映射长度不会自动代表一个稳定的增长快照。持续追加日志更适合轮询文件大小并建立新窗口,同时处理轮转和截断;不要让映射期间的尺寸变化处于无协调状态。

Python mmap 分段处理的关键不是把窗口设得多小,而是分清三组边界:系统要求的对齐边界、当前业务窗口的逻辑边界,以及记录本身的分隔边界。对齐 offset、限制解析范围、保留 carry、及时关闭映射,这四点组合起来,才能稳定处理真正超过内存的大文件。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
CrossOriginProtection 为什么拒绝没有 Origin 的请求CrossOriginProtection 为什么拒绝没有 Origin 的请求
上一篇
CrossOriginProtection 为什么拒绝没有 Origin 的请求
Linux pidfd 如何避免按 PID 操作进程的竞态
下一篇
Linux pidfd 如何避免按 PID 操作进程的竞态
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    386次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    466次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    475次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    415次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    240次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码