用Python更简洁地读取/解压大文件

19 投票
2 回答
37308 浏览
提问于 2025-04-17 14:29

我有一些非常大的 .gz 文件,解压后每个文件大约有 10 到 20 GB。

我需要逐行读取这些文件,所以我用的是标准的方法:

import gzip
f = gzip.open(path+myFile, 'r')
for line in f.readlines():
    #(yadda yadda)
f.close()

但是,open()close() 这两个命令耗时非常长,几乎占用了 98% 的内存和 CPU。严重到程序直接退出,并在终端上显示 Killed。可能是因为它把整个解压后的文件都加载到内存里了?

现在我使用的是类似这样的方式:

from subprocess import call
f = open(path+'myfile.txt', 'w')
call(['gunzip', '-c', path+myfile], stdout=f)
#do some looping through the file
f.close()
#then delete extracted file

这样可以工作。但是有没有更简单的方法呢?

2 个回答

2

你可以看看 pandas,特别是它的输入输出工具。这些工具在读取文件时支持gzip压缩,而且你可以分块读取文件。此外,pandas的速度非常快,使用内存也很高效。

我自己没有尝试过,所以不太清楚压缩和分块读取这两者结合起来效果如何,但我觉得可以试试看。

62

我几乎可以肯定,你的问题不在于 gzip.open(),而是在于 readlines()

正如文档所解释的:

f.readlines() 会返回一个包含文件中所有数据行的列表。

显然,这需要读取并解压整个文件,然后生成一个非常庞大的列表。

很可能,真正耗时的是分配所有这些内存的 malloc 调用。而且,假设你在使用 CPython,到了这个范围的结束,它还需要对这个巨大的列表进行垃圾回收,这也会花费很长时间。

你几乎永远不想使用 readlines。除非你在使用非常老的 Python,否则可以这样做:

for line in f:

一个 file 是一个可迭代的对象,里面充满了行,就像 readlines 返回的 list 一样——不过它实际上并不是一个 list,而是通过从缓冲区读取动态生成更多行。所以,在任何给定的时刻,你只会有一行和几个大约 10MB 的缓冲区,而不是一个 25GB 的 list。而且,读取和解压的过程会在循环的整个生命周期中分散进行,而不是一次性完成。

通过快速测试,使用一个 3.5GB 的 gzip 文件,gzip.open() 几乎是瞬间完成,for line in f: pass 需要几秒钟,gzip.close() 也是瞬间完成。但如果我使用 for line in f.readlines(): pass,那就需要……好吧,我不确定需要多长时间,因为大约一分钟后,我的系统就开始疯狂交换内存,最后不得不强制结束解释器才能让它恢复响应……


由于这个问题在这条回答之后又出现了十几次,我写了这篇博客,进一步解释了一些内容。

撰写回答