压缩内存中的文件,计算校验和并将其写入 python 中的 `gzip`
Compress a file in memory, compute checksum and write it as `gzip` in python
我想压缩文件并使用 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
但是会导致压缩文件被写入然后re-read。对于许多文件(> 10 000),每个文件压缩时数 MB,在 NFS 安装的驱动器中,速度很慢。
如何在缓冲区中压缩文件,然后在写入输出文件之前从此缓冲区计算校验和?
文件不是很大,所以我可以负担得起将所有内容都存储在内存中。然而,一个不错的增量版本也可能不错。
最后一个要求是它应该与多处理一起工作(以便并行压缩多个文件)。
我尝试使用 zlib.compress
但返回的字符串缺少 gzip 文件的 header。
编辑:在 之后,我使用了 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
.
无法更改
我想在 read/write 模式下创建一个 gzip.GzipFile
,控制 mtime,但是 gzip.GzipFile
没有这样的模式。
受到的启发,我编写了以下函数,它在header:
中正确设置了文件名和OS (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] + '[=15=]3' + 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
)的算法略有不同。
包装一个 gzip.GzipFile
object around an io.BytesIO
对象。 (在Python 2中,使用cStringIO.StringIO
代替。)关闭GzipFile
后,您可以从BytesIO
对象中检索压缩数据(使用getvalue
) ,对其进行哈希处理,然后将其写入真实文件。
I have tried to use zlib.compress
but the returned string miss the header of a gzip file.
当然可以。这就是 zlib
module and the gzip
模块之间的全部区别; zlib
仅处理 zlib-deflate 压缩而不使用 gzip headers,gzip
处理 zlib-deflate 数据 with gzip headers.
因此,只需调用 gzip.compress
,您编写但未向我们展示的代码应该可以正常工作。
旁注:
with open(filename) as f:
md5 = hashlib.md5(f.read()).hexdigest()
您几乎肯定想在此处以 'rb'
模式打开文件。您不想将 '\r\n'
转换为 '\n'
(如果在 Windows 上),或者将二进制数据解码为 sys.getdefaultencoding()
文本(如果在 Python 3 上) , 所以用二进制模式打开它。
另一个旁注:
不要在二进制文件上使用 line-based 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)
最后一点:
With many files (> 10 000), each several MB when compressed, in a NFS mounted drive, it is slow.
您的系统没有安装在更快的驱动器上的 tmp 目录吗?
在大多数情况下,您不需要真实的文件。要么有 string-based API(zlib.compress
、gzip.compress
、json.dumps
等),要么 file-based API 只需要一个 file-like object,就像一个 BytesIO
.
但是当您确实需要一个真正的临时文件、一个真正的文件描述符和所有东西时,您几乎总是想在临时目录中创建它。 * 在 Python 中,您使用 tempfile
模块执行此操作。
例如:
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()
如果您需要一个实际的文件名,而不是一个文件 object,您可以使用 f_in.name
.
* 一个例外是当您只希望临时文件最终 rename
到永久位置时。在那种情况下,当然,您通常希望临时文件与永久位置位于同一目录中。但是您可以使用 tempfile
轻松做到这一点。只记得传递 delete=False
.
我想压缩文件并使用 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
但是会导致压缩文件被写入然后re-read。对于许多文件(> 10 000),每个文件压缩时数 MB,在 NFS 安装的驱动器中,速度很慢。
如何在缓冲区中压缩文件,然后在写入输出文件之前从此缓冲区计算校验和?
文件不是很大,所以我可以负担得起将所有内容都存储在内存中。然而,一个不错的增量版本也可能不错。
最后一个要求是它应该与多处理一起工作(以便并行压缩多个文件)。
我尝试使用 zlib.compress
但返回的字符串缺少 gzip 文件的 header。
编辑:在 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
.
我想在 read/write 模式下创建一个 gzip.GzipFile
,控制 mtime,但是 gzip.GzipFile
没有这样的模式。
受到
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] + '[=15=]3' + 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
)的算法略有不同。
包装一个 gzip.GzipFile
object around an io.BytesIO
对象。 (在Python 2中,使用cStringIO.StringIO
代替。)关闭GzipFile
后,您可以从BytesIO
对象中检索压缩数据(使用getvalue
) ,对其进行哈希处理,然后将其写入真实文件。
I have tried to use
zlib.compress
but the returned string miss the header of a gzip file.
当然可以。这就是 zlib
module and the gzip
模块之间的全部区别; zlib
仅处理 zlib-deflate 压缩而不使用 gzip headers,gzip
处理 zlib-deflate 数据 with gzip headers.
因此,只需调用 gzip.compress
,您编写但未向我们展示的代码应该可以正常工作。
旁注:
with open(filename) as f:
md5 = hashlib.md5(f.read()).hexdigest()
您几乎肯定想在此处以 'rb'
模式打开文件。您不想将 '\r\n'
转换为 '\n'
(如果在 Windows 上),或者将二进制数据解码为 sys.getdefaultencoding()
文本(如果在 Python 3 上) , 所以用二进制模式打开它。
另一个旁注:
不要在二进制文件上使用 line-based 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)
最后一点:
With many files (> 10 000), each several MB when compressed, in a NFS mounted drive, it is slow.
您的系统没有安装在更快的驱动器上的 tmp 目录吗?
在大多数情况下,您不需要真实的文件。要么有 string-based API(zlib.compress
、gzip.compress
、json.dumps
等),要么 file-based API 只需要一个 file-like object,就像一个 BytesIO
.
但是当您确实需要一个真正的临时文件、一个真正的文件描述符和所有东西时,您几乎总是想在临时目录中创建它。 * 在 Python 中,您使用 tempfile
模块执行此操作。
例如:
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()
如果您需要一个实际的文件名,而不是一个文件 object,您可以使用 f_in.name
.
* 一个例外是当您只希望临时文件最终 rename
到永久位置时。在那种情况下,当然,您通常希望临时文件与永久位置位于同一目录中。但是您可以使用 tempfile
轻松做到这一点。只记得传递 delete=False
.