Python3读取和处理超大文件的操作详解
作者:rs勿忘初心
需求:
小明是一位 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读取和处理超大文件的资料请关注脚本之家其它相关文章!