用Python更简洁地读取/解压大文件
我有一些非常大的 .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 个回答
你可以看看 pandas,特别是它的输入输出工具。这些工具在读取文件时支持gzip压缩,而且你可以分块读取文件。此外,pandas的速度非常快,使用内存也很高效。
我自己没有尝试过,所以不太清楚压缩和分块读取这两者结合起来效果如何,但我觉得可以试试看。
我几乎可以肯定,你的问题不在于 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
,那就需要……好吧,我不确定需要多长时间,因为大约一分钟后,我的系统就开始疯狂交换内存,最后不得不强制结束解释器才能让它恢复响应……
由于这个问题在这条回答之后又出现了十几次,我写了这篇博客,进一步解释了一些内容。