压缩内存中的文件,计算校验和并在python

时间:2015-05-07 23:06:51

标签: python gzip checksum

我想使用python压缩文件并计算压缩文件的校验和。我的第一个天真的尝试是使用2个函数:

def compress_file(input_filename, output_filename):
    f_in = open(input_filename, 'rb')
    f_out = gzip.open(output_filename, 'wb')
    f_out.writelines(f_in)
    f_out.close()
    f_in.close()


def md5sum(filename):
    with open(filename) as f:
        md5 = hashlib.md5(f.read()).hexdigest()
    return md5

但是,它会导致压缩文件被写入然后重新读取。对于许多文件(> 10 000),压缩时每个几MB,在NFS安装的驱动器中,它很慢。

如何在缓冲区中压缩文件,然后在写入输出文件之前从此缓冲区计算校验和?

文件不是很大,所以我可以将所有内容存储在内存中。但是,一个不错的增量版本也可能很好。

最后一个要求是它应该与多处理一起使用(为了并行压缩多个文件)。

我尝试使用zlib.compress,但返回的字符串错过了gzip文件的标头。

编辑:关注@abarnert sggestion,我使用了python3 gzip.compress

def compress_md5(input_filename, output_filename):
    f_in = open(input_filename, 'rb')
    # Read in buffer
    buff = f_in.read()
    f_in.close()
    # Compress this buffer
    c_buff = gzip.compress(buff)
    # Compute MD5
    md5 = hashlib.md5(c_buff).hexdigest()
    # Write compressed buffer
    f_out = open(output_filename, 'wb')
    f_out.write(c_buff)
    f_out.close()

    return md5

这会生成一个正确的gzip文件,但每次运行时输出都不同(md5不同):

>>> compress_md5('4327_010.pdf', '4327_010.pdf.gz')
'0d0eb6a5f3fe2c1f3201bc3360201f71'
>>> compress_md5('4327_010.pdf', '4327_010.pdf.gz')
'8e4954ab5914a1dd0d8d0deb114640e5'

gzip程序没有此问题:

 $ gzip -c 4327_010.pdf | md5sum
 8965184bc4dace5325c41cc75c5837f1  -
 $ gzip -c 4327_010.pdf | md5sum
 8965184bc4dace5325c41cc75c5837f1  -

我想这是因为gzip模块在​​创建文件时默认使用当前时间(gzip程序使用我猜测的输入文件的修改)。无法使用gzip.compress更改此内容。

我想在读/写模式下创建一个gzip.GzipFile,控制mtime,但gzip.GzipFile没有这样的模式。

@zwol suggestion的启发我编写了以下函数,该函数在标题中正确设置了文件名和操作系统(Unix):

def compress_md5(input_filename, output_filename):
    f_in = open(input_filename, 'rb')    
    # Read data in buffer
    buff = f_in.read()
    # Create output buffer
    c_buff = cStringIO.StringIO()
    # Create gzip file
    input_file_stat = os.stat(input_filename)
    mtime = input_file_stat[8]
    gzip_obj = gzip.GzipFile(input_filename, mode="wb", fileobj=c_buff, mtime=mtime)
    # Compress data in memory
    gzip_obj.write(buff)
    # Close files
    f_in.close()
    gzip_obj.close()
    # Retrieve compressed data
    c_data = c_buff.getvalue()
    # Change OS value
    c_data = c_data[0:9] + '\003' + c_data[10:]
    # Really write compressed data
    f_out = open(output_filename, "wb")
    f_out.write(c_data)
    # Compute MD5
    md5 = hashlib.md5(c_data).hexdigest()
    return md5

不同运行时的输出相同。此外,file的输出与gzip

相同
$ gzip -9 -c 4327_010.pdf > ref_max/4327_010.pdf.gz
$ file ref_max/4327_010.pdf.gz 
ref_max/4327_010.pdf.gz: gzip compressed data, was "4327_010.pdf", from Unix, last modified: Tue May  5 14:28:16 2015, max compression
$ file 4327_010.pdf.gz 
4327_010.pdf.gz: gzip compressed data, was "4327_010.pdf", from Unix, last modified: Tue May  5 14:28:16 2015, max compression

然而,md5是不同的:

$ md5sum 4327_010.pdf.gz ref_max/4327_010.pdf.gz 
39dc3e5a52c71a25c53fcbc02e2702d5  4327_010.pdf.gz
213a599a382cd887f3c4f963e1d3dec4  ref_max/4327_010.pdf.gz

gzip -l也有所不同:

$ gzip -l ref_max/4327_010.pdf.gz 4327_010.pdf.gz 
     compressed        uncompressed  ratio uncompressed_name
        7286404             7600522   4.1% ref_max/4327_010.pdf
        7297310             7600522   4.0% 4327_010.pdf

我想这是因为gzip程序和python gzip模块(基于C库zlib)的算法略有不同。

2 个答案:

答案 0 :(得分:1)

gzip.GzipFile对象周围包裹io.BytesIO个对象。 (在Python 2中,改为使用cStringIO.StringIO。)关闭GzipFile后,可以从BytesIO对象中检索压缩数据(使用getvalue),哈希,并将其写入真实文件。

顺便提一下,您really shouldn't be using MD5 at all anymore.

答案 1 :(得分:1)

  

我尝试使用zlib.compress,但返回的字符串错过了gzip文件的标头。

当然。这是zlib模块和gzip模块之间的全部差异; zlib只处理没有gzip标头的zlib-deflate压缩,gzip使用 gzip标头处理zlib-deflate数据

所以,只需要调用gzip.compress,而你编写但未向我们展示的代码应该正常工作。

作为旁注:

with open(filename) as f:
    md5 = hashlib.md5(f.read()).hexdigest()

您几乎肯定想在此处以'rb'模式打开文件。您不希望将'\r\n'转换为'\n'(如果在Windows上),或将二进制数据解码为sys.getdefaultencoding()文本(如果在Python 3上),因此请以二进制模式打开它。

另一方面说明:

不要在二进制文件上使用基于行的API。而不是:

f_out.writelines(f_in)

......这样做:

f_out.write(f_in.read())

或者,如果文件太大而无法一次性读入内存:

for buf in iter(partial(f_in.read, 8192), b''):
    f_out.write(buf)

最后一点:

  

对于许多文件(> 10 000),压缩时每个几MB,在NFS安装的驱动器中,它很慢。

您的系统是否没有在更快的驱动器上安装tmp目录?

在大多数情况下,您不需要真实的文件。要么基于字符串的API(zlib.compressgzip.compressjson.dumps等),要么基于文件的API只需要类似文件的对象,例如{{1} }。

但当你需要一个真正的临时文件,带有真正的文件描述符和所有内容时,你几乎总是想在临时目录中创建它。 * 在Python中,您使用tempfile模块执行此操作。

例如:

BytesIO

如果您需要实际文件名而不是文件对象,则可以使用def compress_and_md5(filename): with tempfile.NamedTemporaryFile() as f_out: with open(filename, 'rb') as f_in: g_out = gzip.open(f_out) g_out.write(f_in.read()) f_out.seek(0) md5 = hashlib.md5(f_out.read()).hexdigest()

*一个例外是当您只希望临时文件最终f_in.name到永久位置时。当然,在这种情况下,您通常希望临时文件与永久位置位于同一目录中。但你可以轻松地使用rename来做到这一点。请记住通过tempfile