Python3:读取和处理超大文件

在日常工作中,文件对象是我们常接触到的可迭代类型之一。一般用 for 循环遍历一个文件对象,可以逐行读取它的内容。但这种方式在碰到大文件时,可能会出现一些奇怪的效率问题。

需求:

小明是一位 Python 初学者,在学习了如何用 Python 读取文件后,他想要做一个小练习:计算某个文件中数字字符(0~9)的数量。

场景1:小文件处理

假设现在有一个测试用的小文件 small_file.txt,里面包含了一行行的随机字符串:

bash 复制代码
feiowe9322nasd9233rl
aoeijfiowejf8322kaf9a
...

代码示例:file_process.py

bash 复制代码
def count_digits(fname):
    """计算文件里包含多少个数字字符"""
    count = 0
   
     with open(fname) as file:
        for line in file:
            for s in line:
                if s.isdigit():
                    count += 1
    return count


fname = "./small_file.txt"
print(count_digits(fname))

运行结果:

bash 复制代码
# 运行脚本
python3 ./file_process.py

# 输出结果
13

场景2:大文件处理

假设现在我们的大文件big_file.txt,大小有5G,且所有的文本都在一行。

大文件 big_file.txt

bash 复制代码
df2if283rkwefh... <剩余 5 GB 大小> ...

却发现同样的程序花费了一分多钟才给出结果,并且整个执行过程耗光了笔记本电脑的全部 4G 内存。

问题分析:

为什么同一份代码用于大文件时,效率就会变低这么多呢?原因就藏在小明读取文件的方法里。

在代码里所使用的文件读取方式,可谓 Python 里的"标准做法":首先用 with open (fine_name) 上下文管理器语法获得一个文件对象,然后用 for 循环迭代它,逐行获取文件里的内容。为什么这种文件读取方式会成为标准?这是因为它有两个好处:

(1) with 上下文管理器会自动关闭文件描述符

(2) 在迭代文件对象时,内容是一行一行返回的,不会占用太多内存。

不过这套标准做法虽好,但不是没有缺点。假如被读取的文件里根本就没有任何换行符,那么上面列的第 (2) 个好处就不再成立。**缺少换行符以后,程序遍历文件对象时就不知道该何时中断,最终只能一次性生成一个巨大的字符串对象,白白消耗大量时间和内存。**这就是 count_digits() 函数在处理 big_file.txt 时变得异常缓慢的原因。

要解决这个问题,我们需要把这种读取文件的"标准做法"暂时放到一边。

解决方法:

使用 while 循环加 read() 方法分块读取。

除了直接遍历文件对象来逐行读取文件内容外,我们还可以调用更底层的 file.read() 方法。与直接用循环迭代文件对象不同,每次调用 file.read(chunk_size), 会马上读取从当前游标位置往后 chunk_size 大小的文件内容,不必等待任何换行符出现。有了 file.read() 方法的帮助,优化后的代码:

bash 复制代码
def count_digits_v2(fname):
    """计算文件里包含多少个数字字符,每次读取 8 KB"""
    count = 0
    block_size = 1024 * 8
    with open(fname) as file:
        while True:
            chunk = file.read(block_size)
            # 当文件没有更多内容时,read 调用将会返回空字符串 ''
            if not chunk:
                break
            for s in chunk:
                if s.isdigit():
                    count += 1
    return count


fname = "./big_file.txt"
print(count_digits_v2(fname))

在新函数中,我们使用了一个 while 循环来读取文件内容,每次最多读 8 KB,程序不再需要在内存中拼接长达数吉字节的字符串,内存占用会大幅降低。

(吉字节是一种数据存储单位,通常用于表示大容量存储设备的容量大小。它等于1024^3(1,073,741,824)字节,或者1,024兆字节。在计算机领域,常用于描述大型文件、程序或数据集的大小,例如硬盘容量、内存容量等。)

相关推荐
hhzz40 分钟前
Python大数据实战(十六):音乐推荐系统——基于协同过滤算法构建个性化歌单引擎
大数据·人工智能·python·数据挖掘·数据分析
SunnyDays10111 小时前
Python PDF 转 Markdown 详解(转换整个文档,特定页面,表格,扫描 PDF)
python·pdf 转 markdown·pdf 转 md·扫描 pdf 转 md·pdf 表格转 md
2601_954706491 小时前
云手机 API 自动化实战:Python 批量控机、挂机脚本完整实现
python·智能手机·自动化
Alan_6915 小时前
聊聊 Swagger/Postman/Apifox 的真实选型与 Python 项目实战
python·测试工具·postman
智慧物业老杨6 小时前
物业绿化数智化权责管控方案:基于工单平台的双层权限追溯模块落地实践
python
Metaphor6927 小时前
使用 Python 在 Word 文档中添加批注
python·word
FreakStudio7 小时前
WIZnet 开源方案分享:W55MH32 开发板 + 小智 AI实现以太网版本的小智机器人
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
会周易的程序员7 小时前
使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块
java·c++·python·物联网·架构·日志·aiot
一只专注api接口开发的技术猿7 小时前
电商评论自动化监控与情感数据分析完整落地教程(附可直接运行 Python 代码)
大数据·数据库·python·数据分析·自动化
HAPPY酷7 小时前
【ROS2】 Conda 环境搭建(命令行版)
linux·运维·python·ubuntu·机器人·conda