Python - 如何在没有MemoryError的情况下gzip大文本文件?

时间:2014-11-20 08:51:50

标签: python out-of-memory gzip large-files

我使用以下简单的Python脚本在EC2 m3.large实例上压缩大文本文件(例如, 10GB )。但是,我总是得到MemoryError

import gzip

with open('test_large.csv', 'rb') as f_in:
    with gzip.open('test_out.csv.gz', 'wb') as f_out:
        f_out.writelines(f_in)
        # or the following:
        # for line in f_in:
        #     f_out.write(line)

我得到的追溯是:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    f_out.writelines(f_in)
MemoryError

我已经阅读了一些关于这个问题的讨论,但仍然不太清楚如何处理这个问题。有人能给我一个关于如何处理这个问题的更容易理解的答案吗?

3 个答案:

答案 0 :(得分:8)

这里的问题与gzip没有任何关系,以及从10GB文件中逐行读取而没有新行的所有内容:

  

另外请注意,我用来测试Python gzip功能的文件是由fallocate -l 10G bigfile_file生成的。

它为您提供了一个完全由0字节组成的10GB稀疏文件。意思是没有换行字节。意思是第一行是10GB长。这意味着读取第一行需要10GB。 (或者甚至可能是20或40GB,如果您使用3.3之前的Python并尝试将其作为Unicode读取。)

如果要复制二进制数据,请不要逐行复制。无论它是普通文件,GzipFile即时为您解压缩,socket.makefile()还是其他任何内容,您都会遇到同样的问题。

解决方案是按块复制块。或者只使用copyfileobj,它会自动为您执行此操作。

import gzip
import shutil

with open('test_large.csv', 'rb') as f_in:
    with gzip.open('test_out.csv.gz', 'wb') as f_out:
        shutil.copyfileobj(f_in, f_out)

默认情况下,copyfileobj使用经过优化的块大小,通常非常好并且从不非常糟糕。在这种情况下,您可能实际上需要更小的尺寸或更大的尺寸;很难预测哪个先验。*因此,使用timeit使用不同的bufsize参数(例如,从1KB到8MB的4的幂)到copyfileobj来测试它。但是默认的16KB可能足够好,除非你做了很多这样的事情。

*如果缓冲区大小太大,您可能最终会交替长时间的I / O块和长时间的处理块。如果它太小,您最终可能需要多次读取才能填充单个gzip块。

答案 1 :(得分:7)

那很奇怪。如果你试图压缩一个没有包含很多换行符的大型二进制文件,我会期待这个错误,因为这样的文件可能包含一个&#34; line&#34;这对你的RAM来说太大了,但它不应该发生在一个行结构的.csv文件中。

但无论如何,逐行压缩文件效率不高。即使操作系统缓冲磁盘I / O,它通常很多更快地读取和写入更大的数据块,例如64 kB。

我在这台机器上有2GB的RAM,我刚刚成功使用下面的程序来压缩2.8GB的tar存档。

#! /usr/bin/env python

import gzip
import sys

blocksize = 1 << 16     #64kB

def gzipfile(iname, oname, level):
    with open(iname, 'rb') as f_in:
        f_out = gzip.open(oname, 'wb', level)
        while True:
            block = f_in.read(blocksize)
            if block == '':
                break
            f_out.write(block)
        f_out.close()
    return


def main():
    if len(sys.argv) < 3:
        print "gzip compress in_file to out_file"
        print "Usage:\n%s in_file out_file [compression_level]" % sys.argv[0]
        exit(1)

    iname = sys.argv[1]
    oname = sys.argv[2]
    level = int(sys.argv[3]) if len(sys.argv) > 3 else 6

    gzipfile(iname, oname, level)


if __name__ == '__main__':  
    main()

我正在运行Python 2.6.6而gzip.open()并不支持with


Andrew Bay在评论中指出,if block == '':在Python 3中无法正常工作,因为block包含字节,而不是字符串,而空字节对象不包含比较等于空文本字符串。我们可以检查块长度,或者与b''(也可以在Python 2.6+中使用)进行比较,但简单的方法是if not block:

答案 2 :(得分:3)

即使逐行读取文件也会出现内存错误。我想这是因为你有很少的可用内存和非常大的行。然后你应该使用二进制读取:

import gzip

#adapt size value : small values will take more time, high value could cause memory errors
size = 8096

with open('test_large.csv', 'rb') as f_in:
    with gzip.open('test_out.csv.gz', 'wb') as f_out:
        while True:
            data = f_in.read(size)
            if data == '' : break
            f_out.write(data)