Python - 如何在没有 MemoryError 的情况下压缩大型文本文件



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

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

这里的问题与 gzip 无关,而与从没有换行符的 10GB 文件中逐行读取有关:

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

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

如果要复制二进制数据,请不要逐行复制。无论是普通文件、即时为您解压缩的GzipFilesocket.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使用经过优化的块大小,通常非常好,永远不会很差。在这种情况下,您实际上可能想要更小或更大的尺寸;很难预测哪个是先验的。 因此,通过使用具有不同bufsize参数(例如,从 1KB 到 8MB 的 4 的幂)到 copyfileobjtimeit来测试它。但是默认的 16KB 可能已经足够了,除非您做了很多这样的事情。

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

很奇怪。如果您尝试压缩不包含很多换行符的大型二进制文件,我可能会遇到此错误,因为此类文件可能包含对您的 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:

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

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)

相关内容

最新更新