9.3. gzip — GNU zip 文件的读与写
目的:读写 gzip 文件。
gzip
模块提供了类文件的接口用于处理 GNU zip 文件,使用 zlib
压缩或者解压数据。
写压缩文件
模块级别的函数 open()
创建一个类文件的 GzipFile
的实例。这个常用的方法用于读写提供的字节。
gzip_write.py
import gzip
import io
import os
outfilename = 'example.txt.gz'
with gzip.open(outfilename, 'wb') as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.write('Contents of the example file go here.\n')
print(outfilename, 'contains', os.stat(outfilename).st_size,
'bytes')
os.system('file -b --mime {}'.format(outfilename))
为了向压缩文件写入数据,以 wb
模式打开文件。这个例子使用来自 io
模块的 TextIOWrapper
包装了 GzipFile
,能够将 Unicode 文本编码成适用压缩的字节。
$ python3 gzip_write.py
application/x-gzip; charset=binary
example.txt.gz contains 75 bytes
通过 compresslevel
参数,可以使用不同的压缩级别。compresslevel
有效值范围是0到9(包括0和9)。较小的压缩级别将带来更快的压缩速度,但导致压缩量较小。值越大,压缩越慢,但是压缩比更高。
gzip_compresslevel.py
import gzip
import io
import os
import hashlib
def get_hash(data):
return hashlib.md5(data).hexdigest()
data = open('lorem.txt', 'r').read() * 1024
cksum = get_hash(data.encode('utf-8'))
print('Level Size Checksum')
print('----- ---------- ---------------------------------')
print('data {:>10} {}'.format(len(data), cksum))
for i in range(0, 10):
filename = 'compress-level-{}.gz'.format(i)
with gzip.open(filename, 'wb', compresslevel=i) as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.write(data)
size = os.stat(filename).st_size
cksum = get_hash(open(filename, 'rb').read())
print('{:>5d} {:>10d} {}'.format(i, size, cksum))
输出中中间一列的数字表示压缩输入生成的文件中包含字节的多少。对于这个输入数据,较高的压缩值并没有带来理想的压缩效果。结果是变化的,取决于输入。
$ python3 gzip_compresslevel.py
Level Size Checksum
----- ---------- ---------------------------------
data 754688 e4c0f9433723971563f08a458715119c
0 754793 ced7189c324eb73a8388492a9024d391
1 9846 5356d357f23e0d5b6d85e920929f0e43
2 8267 8ce46bce238edc095e47e941cebad93d
3 8227 91662517459db94a744671a6b4295b67
4 4167 ad304e3aec585640de9f14306fb32083
5 4167 4381a5d6dff4dd2746387f20411dcfcd
6 4167 ef3a05112ea382abb53bc4a5bee3a52a
7 4167 4723a253d1dc8ddecd4ff7b7adf0bc0b
8 4167 0e1aeba7bdc39f0007039f130d9a28b2
9 4167 eccf47c4c4f1cca3274e57a1b9b9ddd2
GzipFile
实例也包含了一个 writelines()
方法,能够被用于写入字符串序列。
gzip_writelines.py
import gzip
import io
import itertools
import os
with gzip.open('example_lines.txt.gz', 'wb') as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.writelines(
itertools.repeat('The same line, over and over.\n',
10)
)
os.system('gzcat example_lines.txt.gz')
同常规文件一样,输入的行需要包含一个新行字符,这里是:\n
。
$ python3 gzip_writelines.py
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
读取压缩数据
要从先前压缩的文件中读取数据,以二进制模式('rb'
)打开文件,这样就不会执行基于文本的行尾转换或执行 Unicode 解码。
gzip_read.py
import gzip
import io
with gzip.open('example.txt.gz', 'rb') as input_file:
with io.TextIOWrapper(input_file, encoding='utf-8') as dec:
print(dec.read())
这个例子读取上一节中 gzip_write.py
写的文件,在解压缩后使用TextIOWrapper
解码文本。
$ python3 gzip_read.py
Contents of the example file go here.
在读取文件时,也可以仅查找和读取部分数据。
gzip_seek.py
import gzip
with gzip.open('example.txt.gz', 'rb') as input_file:
print('Entire file:')
all_data = input_file.read()
print(all_data)
expected = all_data[5:15]
# 回到开头
input_file.seek(0)
# 向前移动 5 个字节
input_file.seek(5)
print('Starting at position 5 for 10 bytes:')
partial = input_file.read(10)
print(partial)
print()
print(expected == partial)
seek()
相对于 未压缩的 数据的位置,因此调用者不需要知道数据文件是否被压缩。
$ python3 gzip_seek.py
Entire file:
b'Contents of the example file go here.\n'
Starting at position 5 for 10 bytes:
b'nts of the'
True
处理流
GzipFile
类可以用于去包装其他类型的数据流,因此他们也可以使用压缩。这在通过套接字或者现有的(已打开)文件传输数据时很有用。 BytesIO
缓冲池也可以这么用。
gzip_BytesIO.py
import gzip
from io import BytesIO
import binascii
uncompressed_data = b'The same line, over and over.\n' * 10
print('UNCOMPRESSED:', len(uncompressed_data))
print(uncompressed_data)
buf = BytesIO()
with gzip.GzipFile(mode='wb', fileobj=buf) as f:
f.write(uncompressed_data)
compressed_data = buf.getvalue()
print('COMPRESSED:', len(compressed_data))
print(binascii.hexlify(compressed_data))
inbuffer = BytesIO(compressed_data)
with gzip.GzipFile(mode='rb', fileobj=inbuffer) as f:
reread_data = f.read(len(uncompressed_data))
print('\nREREAD:', len(reread_data))
print(reread_data)
使用 GzipFile
而不是 zlib
的一个好处是它支持文件 API。然而,当重新读取先前压缩的数据时,需要给 read()
方法显式传入一个长度。去掉长度将导致一个 CRC 错误,可能是因为 BytesIO
在遇到 EOF 之前反悔了空字符串。处理压缩数据流的时候,要么使用表示要读取的实际数据量的整数作为数据前缀,要么使用zlib中的增量解压缩API。
$ python3 gzip_BytesIO.py
UNCOMPRESSED: 300
b'The same line, over and over.\nThe same line, over and over.\nT
he same line, over and over.\nThe same line, over and over.\nThe
same line, over and over.\nThe same line, over and over.\nThe sam
e line, over and over.\nThe same line, over and over.\nThe same l
ine, over and over.\nThe same line, over and over.\n'
COMPRESSED: 51
b'1f8b080022caae5a02ff0bc94855284ecc4d55c8c9cc4bd551c82f4b2d5248c
c4b0133f4b8424665916401d3e717802c010000'
REREAD: 300
b'The same line, over and over.\nThe same line, over and over.\nT
he same line, over and over.\nThe same line, over and over.\nThe
same line, over and over.\nThe same line, over and over.\nThe sam
e line, over and over.\nThe same line, over and over.\nThe same l
ine, over and over.\nThe same line, over and over.\n'
推荐阅读
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。