python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python3读取和处理超大文件

Python3读取和处理超大文件的操作详解

作者:rs勿忘初心

在日常工作中,文件对象是我们常接触到的可迭代类型之一,一般用 for 循环遍历一个文件对象,可以逐行读取它的内容,但这种方式在碰到大文件时,可能会出现一些奇怪的效率问题,所以本文给大家介绍了Python3读取和处理超大文件的操作,需要的朋友可以参考下

需求:

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

场景1:小文件处理

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

feiowe9322nasd9233rl
aoeijfiowejf8322kaf9a
...

代码示例:file_process.py

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))

运行结果:

# 运行脚本
python3 ./file_process.py
 
# 输出结果
13

场景2:大文件处理

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

大文件 big_file.txt

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() 方法的帮助,优化后的代码:

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兆字节。在计算机领域,常用于描述大型文件、程序或数据集的大小,例如硬盘容量、内存容量等。)

拓展:用Python读取超大文件中的部分行

Python文件读取一直是python的常见用法,通用方法是直接readlines加载所有行。
但是,对于超大文件(如100G的tsv),直接加载所有行会非常慢。

如果是想遍历整个文件并处理每一行,其实并不需要一次加载所有行。

这里用迭代器的方法来读取文件:

file_name = 'all_items.tsv'
start_line = 110000
end_line = 120000
with open(file_name) as f:
	for i in range(0, start_line):
        next(f)
    lines = [next(f) for i in range(start_line, end_line)]
print(len(lines))

对于大文件all_items.tsv,咱们只读取某一个区间的行来进行处理。先用迭代器滚动到start_line的位置,再开始读取。
如果咱们可以利用迭代器,把这个文件的行遍历一遍(并对行进行处理):

file_name = 'all_items.tsv'
def process_line(line):
    return line
with open(file_name) as f:
    while True:
        try:
            line = next(f)
            process_line(line)
        except StopIteration:
            break

用一个while循环就可以完成从头到尾行的遍历。

以上就是Python3读取和处理超大文件的操作详解的详细内容,更多关于Python3读取和处理超大文件的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文