在Python中更简洁的读取/解压大文件的方法

19

我有一些非常巨大的 .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

这个方法可行。但是是否有更简洁的方式呢?


1
你确定是 open 而不是 readlines 卡住了吗? - abarnert
2个回答

62

我99%确定您的问题不在于gzip.open(),而在于readlines()

正如文档中所解释的:

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

显然,这需要读取和解压缩整个文件,并建立一个绝对巨大的列表。

很可能实际上是使用malloc调用分配所有内存所需的时间太长。然后,在该作用域的末尾(假设您正在使用CPython),它必须回收整个巨大的列表,这也将花费很长时间。

您几乎永远不需要使用readlines。除非您使用的是非常旧的Python,否则只需执行以下操作:

for line in f:
文件是一个包含行的可迭代对象,就像由readlines返回的list一样——除了它实际上不是一个list,它通过从缓冲区中读取来动态生成更多的行。因此,在任何时候,您只会有一行和大约10MB左右的几个缓冲区,而不是25GB的list。并且读取和解压缩将在循环的生命周期内分散进行,而不是一次性完成。 经过快速测试,对于一个3.5GB的gzip文件,gzip.open() 函数几乎瞬间完成,for line in f: pass 则需要几秒钟时间,gzip.close() 函数也几乎瞬间完成。但如果我执行 for line in f.readlines(): pass,那么需要花费很长时间,我不确定具体需要多长时间,因为大约一分钟后我的系统进入了交换空间的恶性循环,我必须强制停止解释器才能使其响应任何操作。
自从写下这篇文章以来,这种情况已经发生了十几次以上,因此我写了这篇博客文章,其中对此进行了更详细的解释。

@shihpeng for line in f: 是一个pythonic和正确的回答。你有其他的信息吗? - FirefighterBlu3
@FirefighterBlu3,您提到了一条四年前的评论,该评论提到的答案被证明是错误的,并由回答者删除。最好的做法可能是将其标记为不再需要或忽略它,而不是回复它。(如果您无法阅读已删除的答案,则 shihpeng 的问题在于他实际上没有文本数据,而是二进制数据,这些数据在许多兆字节中没有 \x0a 字节。答案是不要将二进制数据读取为文本...) - abarnert

2
请看pandas的IO工具,它们支持读取压缩文件并且可以分块读取文件。此外,pandas非常快速和内存高效。虽然我没有尝试过同时使用压缩和分块读取的功能,但是这个方法值得一试。

gzip.open 具有完美的缓冲区,因此您不需要显式地分块读取;只需使用正常的类似文件的 API 以最合适的方式读取它(例如 for line in f:for row in csv.reader(f) 或者甚至是带有大小提示而不是无参数的 readlines)。而且它也非常快速和内存高效。据我所知,OP 的代码仅因为 readlines 而成为内存占用量大的程序,并且仅因为这种内存占用而变慢。 - abarnert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接