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

2024-05-13 21:27:40 发布

您现在位置:Python中文网/ 问答频道 /正文

我使用以下简单的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

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


Tags: csvintest脚本writelinesaswithline
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)

真奇怪。如果您试图压缩一个不包含许多换行符的大型二进制文件,我会预料到这个错误,因为这样的文件可能包含一个对RAM来说太大的“行”,但它不应该出现在一个行结构的.csv文件上。

但是无论如何,逐行压缩文件不是很有效。即使操作系统缓冲磁盘I/O,但读取和写入更大的数据块(如64KB)通常要快得多。

我在这台机器上有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:

这里的问题与gzip无关,与逐行读取10GB文件(其中没有新行)有关:

As an additional note, the file I used to test the Python gzip functionality is generated by fallocate -l 10G bigfile_file.

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

如果要复制二进制数据,不要逐行复制。无论是一个普通的文件,一个正在为您动态解压缩的GzipFile,一个socket.makefile(),或者其他什么,您都会遇到同样的问题。

解决方案是逐块复制。或者只使用^{},这会自动为您实现。

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块。

相关问题 更多 >