首页 > 编程语言 >Python | import mmap模块详解(处理大文本)

Python | import mmap模块详解(处理大文本)

时间:2023-07-03 22:33:28浏览次数:47  
标签:文件 映射 Python mmap read 内存 import os

如果现在有一个需求,我们需要处理一个20G的大文件,我们会怎么处理呢?思考下,我们需要怎么实现这个功能。

我们可能会这么实现:

def get_datas():
    source_text_path = "路径"
    with open(source_text_path, 'rb') as f:
        data = f.readlines()
    yield data
 
 
if __name__ == '__main__':
    for e in get_datas():
        deal_data(e)  # 处理数据

这样虽然能实现,但是我们处理的时候需要消耗的资源和性能不是很友好,所以我们要优化,也就是使用mmap模块。

mmap是一种虚拟内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一映射关系。它省掉了内核态和用户态页copy这个动作(两态间copy),直接将用户态的虚拟地址与内核态空间进行映射,进程直接读取内核空间,速度提高了,内存占用也少了。

简单点来说,mmap函数实现的是内存共享。内存共享是两个不同的进程共享内存的意思:同一块物理内存被映射到两个进程的各自的进程地址空间。这个物理内存已经被规定了大小(大小一定要比实际写入的东东大)以及名称。当需要写入时,找到内存名称,然后写入内存,等需要读取时候, 首先要知道你要读取多大(因为物理内存比你要读取的东西大,全部读取的话会读到一些“空”的东西),然后寻找对应名称的物理块,然后读取。

mmap 介绍

Windows

mmap.mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT[, offset])

参数说明:

  • fileno:文件描述符,可以是file对象的fileno()方法,或者来自os.open(),在调用mmap()之前打开文件,不再需要文件时要关闭。
  • length:要映射文件部分的大小(以字节为单位),这个值为0,则映射整个文件,如果大小大于文件当前大小,则扩展这个文件。
  • tagname:为映射提供标签名称的字符串,Windows 允许你对同一文件拥有许多不同的映射。如果指定现有标签的名称,则会打开该标签,否则将创建该名称的新标签。如果省略此参数或设置为None ,则创建的映射不带名称。避免使用tag参数将有助于使代码在Unix和Windows之间可移植。
  • access:文件权限
    • ACCESS_READ:读访问;
    • ACCESS_WRITE:写访问,默认;
    • ACCESS_COPY:拷贝访问,不会把更改写入到文件,使用flush把更改写到文件。
  • offset:非负整数偏移量,默认从0开始。

Unix

mmap.mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, access=ACCESS_DEFAULT[, offset])

参数说明:

  • flags:映射的性质,默认MAP_SHARED
    • MAP_PRIVATE 会创建私有的写入时拷贝映射,因此对mmap对象内容的修改将为该进程所私有;
    • MAP_SHARED 会创建与其他映射同一文件区域的进程所共享的映射。
  • prot:它将给出所需的内存保护方式;最有用的两个值是 PROT_READPROT_WRITE,分别指明页面为可读或可写。 prot 默认为PROT_READ | PROT_WRITE
  • access:注意的是可以指定 access 作为替代 flagsprot 的可选关键字形参。 同时指定 flagsprotaccess 将导致错误。

fileno文件描述符有如下

  • os.O_RDONLY:以只读的方式打开Readonly
  • os.O_WRONLY:以只写的方式打开Write only
  • os.O_RDWR:以读写的方式打开 Read and write
  • os.O_APPEND:以追加的方式打开
  • os.O_CREAT:创建并打开一个新文件
  • os.O_EXCLos.O_CREAT| os.O_EXCL 如果指定的文件存在,返回错误
  • os.O_TRUNC:打开一个文件并截断它的长度为零(必须有写权限)
  • os.O_BINARY:以二进制模式打开文件(不转换)
  • os.O_NOINHERIT:阻止创建一个共享的文件描述符
  • os.O_SHORT_LIVED
  • os.O_TEMPORARY:与O_CREAT一起创建临时文件
  • os.O_RANDOM:缓存优化,但不限制从磁盘中随机存取
  • os.O_SEQUENTIAL :缓存优化,但不限制从磁盘中序列存取
  • os.O_TEXT:以文本的模式打开文件(转换)

支持的方法

  • close(): 关闭 mmap。 后续调用该对象的其他方法将导致引发 ValueError 异常。 此方法将不会关闭打开的文件。
  • closed: 如果文件已关闭则返回 True
  • find(str, start, end): 从 start 下标开始,在 m中从左往右寻找子串 str最早出现的下标;没有找到则返回-1。
  • flush([offset, n]):将对文件的内存副本的修改刷新至磁盘。 如果不使用此调用则无法保证在对象被销毁前将修改写回存储。 如果指定了offsetsize,则只将对指定范围内字节的修改刷新至磁盘;在其他情况下,映射的全部范围都会被刷新。
    • windows: 返回的非零值表示成功;否则返回0。 零表示失败。
    • unix: 返回零值以表示成功。 当调用失败时将引发异常。
  • move(dest, src, count): 将从偏移量 src开始的 count个字节拷贝到目标索引号 dest。 如果 mmap 创建时设置了 ACCESS_READ,则调用 move将引发异常。
  • read([n]): 返回一个字节,其中包含从当前文件位置开始的至多 n 个字节。 如果参数省略,为 None 或负数,则返回从当前文件位置开始直至映射结尾的所有字节。 文件位置会被更新为返回字节数据之后的位置
  • read_byte():返回一个1字节长的字符串,从 m 对应的文件中读1个字节,要是已经到了EOF还调用 read_byte(),则抛出异常 ValueError
  • readline():返回一个字符串,从 m 对应文件的当前位置到下一个’\n’,当调用 readline() 时文件位于 EOF,则返回空字符串。
  • resize(newsize):如果存在的话, 改变映射以及下层文件的大小。 如果 mmap 创建时设置了 ACCESS_READACCESS_COPY,则改变映射大小将引发异常。
  • rfind(sub[, start[, end]]):返回子序列 sub在对象内被找到的最大索引号,使得 sub 被包含在 [start, end] 范围中。 可选参数 startend 会被解读为切片表示法。 如果未找到则返回 -1。
  • seek(pos[, whence]):设置文件的当前位置。 whence 参数为可选项并且默认为 os.SEEK_SET 或 0 (绝对文件定位);其他值还有 os.SEEK_CUR 或 1 (相对当前位置查找) 和 os.SEEK_END 或 2 (相对文件末尾查找)。
  • size():返回文件的长度,该数值可以大于内存映射区域的大小。
  • tell():返回文件指针的当前位置。
  • write(str):将str写入文件指针当前位置的内存并返回写入的字节总数 (一定不小于 len(str),因为如果写入失败,将会引发错误)。 在字节数据被写入后文件位置将会更新。 如果 mmap 创建时设置了 ACCESS_READ,则向其写入将引发 异常
  • write_byte(byte):将整数值 byte 写入文件指针当前位置的内存;文件位置前进 1。 如果 mmap 创建时设置了 ACCESS_READ,则向其写入将引发异常。

对于EOF的处理,write()read_byte() 抛出异常 ValueError,而 write_byte()read() 什么都不做。

使用mmap读取大文件

from mmap import mmap
 
def read_data(file_path):
    with open(file_path, "r+") as f:
        m = mmap(f.fileno(), 0)
        g_index = 0
        for index, char in enumerate(m):
            if char == b"\n":
                yield m[g_index:index + 1].decode()
                g_index = index + 1
 
 
if __name__ == "__main__":
    file_path = ""
    for content in read_data(file_path):
        print(content)

什么时候用mmap?

用mmap来读取超大文件,不是mmap的主要应用场景,Python官方文件也没有提到这一点。如果仅仅是读取超大文件,使用文件对象的read(N),来得更快更好更简单。

关于标准库中的mmap模块。现有一个需求,要对超大文件(接近40G)进行读写,notepad++等工具直接拒绝打开此文件。用 r+ 模式打开文件,可以随意读写,但是要特别小心。readline()是否能够使用,要看这个文件每行都多长,如果没有换行,就不能用,就算知道每行的大小,也要带个参数N来控制最大读取数量。readlines()是肯定不能用的,就算带参数,也可能直接卡死!read(N)没问题,主要控制是N的大小。

总之,传统读写文件的方式可以用,但是不够方便。速度也是个问题,传统的缓存IO方式,涉及到OS内核态的内存和进程虚拟空间内存的内容交换,对于超大文件而言,这种交换会浪费大量的CPU时间和内存。mmap是另一个方式!它省掉了内核态和用户态页拷贝这个动作(两态间copy),直接将用户态的虚拟地址与内核态空间进行映射,进程直接读取内核空间,速度提高了,内存占用也少了。

总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

而使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互省去了空间不同数据不通的繁琐过程。因此在某些场景下,mmap效率更高。

从python官网上看mmap的介绍,生成的mmap对象,就像一个bytearray对象,可以直接用index的方式读写,可以切片。同时,mmap对象还有一组类似文件操作的接口,readreadlineflush等等。即mmap对象兼具bytearrayfile对象的功能。不过还是要注意,对于超大文件的读(先不考虑写的问题吧),从磁盘到内核,依然会占用内存,因此绝对不能一口气全部读出来。read(N)是必须的,mmap的使用只是可能会提高效率。(如果频繁的创建和关闭mmap映射,这种创建是为了指向超大文件的不同位置,反而效率更低。一般情况下的read(N)实现,不需要使用mmap。)

mmap的另一个应用场景,是进程间的内存共享。多个进程将同一个文件map到同一段内核地址上,即实现了相互之间的共同访问。

总结:使用mmap的时机

  1. 将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存映射读写取代I/O缓存读写,以获得较高的性能;
  2. 将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
  3. 为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。

标签:文件,映射,Python,mmap,read,内存,import,os
From: https://www.cnblogs.com/zhangxuegold/p/17524324.html

相关文章

  • python基础day36 软件开发架构
    软件开发架构网络编程:我们要基于网络来编写一款B/S或者C/S架构的软件,比如ATM,我们现在写的都是单机版本的,没有接入网络的系统,别人是无法访问到的目的:以ATM为例,现在我们想把之前写的ATM系统变成基于网络传输的,别人如果想用,就必须把客户端下载到本地电脑上,以登录为例,用户把用户名......
  • Python web 框架对比:Flask vs Django
    哈喽大家好,我是咸鱼今天我们从几个方面来比较一些现在流行的两个pythonweb框架——Flask和Django,突出它们的主要特性、优缺点和简单案例到最后,大家将更好地了解哪个框架更适合自己的特定需求参考链接:https://djangocentral.com/flask-vs-django-selecting-the-perfect-pyt......
  • flask run 和python xxx 两种方式的区别
    在Flask项目中,flaskrun和pythonxxx(其中xxx是Python文件名)是两种不同的方式来运行Flask应用程序。flaskrun:这是Flask提供的命令行工具,用于在开发环境中运行Flask应用程序。当你在项目目录下运行flaskrun命令时,Flask会自动检测应用程序的入口文件(通常是app......
  • python requests
    参考资料菜鸟Pythonrequests模块PythonRequests库进阶用法——timeouts,retries,hooks中文文档地址:http://cn.python-requests.org/zh_CN/latest/英文文档地址:https://2.python-requests.org/en/master/api/后台接口packagecom.laolang.shop.modules.admin.controller;imp......
  • python连接Oracle数据库实现数据查询并导入MySQL数据库
    1.项目背景由于项目需要连接第三方Oracle数据库,并从第三方Oracle数据库中查询出数据并且显示,而第三方的Oracle数据库是Oracle11的数据库。而django4.1框架支持支持Oracle数据库服务器19c及以上版本,需要7.0或更高版本的cx_OraclePython驱动;django3.2支持Oracle数据库......
  • 简单的python面向对象案例——跑步或吃饭
    个人学习,仅供参考要求对象:小明a.属性:姓名,体重b.方法:跑步,吃东西(每次跑步会减掉0.1kg,每次吃东西增加0.2kg)输入名字以初始体重选择跑步或吃东西,输入次数打印当前体重代码如下:#定义一个类classPerson(object):#公共属性def__init__......
  • 在Jupyter笔记本中使用Python与GPT-4进行交互
    在这篇文章中,我们将讨论如何在Jupyter笔记本中使用Python与GPT-4(一种强大的自然语言处理模型)结合进行处理。尽管OpenAI并未特地发布名为"GPT-4"的模型,但我们可以使用现有的GPT-3作为参考。如OpenAI未来发布了GPT-4,其与GPT-3的用法将会非常相似。在Jupyter笔记本中使用Python与GPT......
  • 记一次python消费kafka进程持续消耗内存问题
    前提:python写了一个kafka消费的脚本,脚本中消费kafka消息并将消费到的数据放在一个线程池中进行业务代码处理,使用supervisor管理这个脚本进程遇到问题:这个进程占用的内存会越来越大,知道将机器内存消耗完排查:网上找了一堆内存分析工具,好像都需要预埋代码,或者重新启动一个进程,全扯......
  • python基础35 网络编程 软件开发架构和七层协议
    软件开发架构网络编程我们要基于网络来编写一款B/S或者C/S架构的软件,比如:ATM,我们写的只是ATM的单机版本,没有接入网络系统,别人无法访问到的目的以ATM为例,现在我们想把之前写的ATM系统编程基于网络传输的,别人如果想用,就必须把客户端下载到本地电脑上,已登录为例,用户把用......
  • Python之Mixins机制
    Mixins机制classVehicle:#交通工具passclassFlyMinix:"""将主类中有多个子类要继承的方法单独拿出来,重新定义一个类,将这个有给有需要的子类继承,在主类中不再写这个方法"""deffly(self):"""跟飞行相关的功能......