我想使用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
)的算法略有不同。
答案 0 :(得分:1)
在gzip.GzipFile
对象周围包裹io.BytesIO
个对象。 (在Python 2中,改为使用cStringIO.StringIO
。)关闭GzipFile
后,可以从BytesIO
对象中检索压缩数据(使用getvalue
),哈希,并将其写入真实文件。
答案 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.compress
,gzip.compress
,json.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
。