9.2. zlib — GNU zlib 压缩
目的:对 GNU zlib 压缩库的低级访问
zlib
模块为GNU项目的 zlib
压缩库中的许多函数提供了一个低级接口。
使用内存中的数据
使用 zlib
的最简单方法是将所有数据保存在内存中进行压缩或解压缩。
zlib_memory.py
import zlib
import binascii
original_data = b'This is the original text.'
print('Original :', len(original_data), original_data)
compressed = zlib.compress(original_data)
print('Compressed :', len(compressed),
binascii.hexlify(compressed))
decompressed = zlib.decompress(compressed)
print('Decompressed :', len(decompressed), decompressed)
compress()
和 decompress()
函数都接受一个字节序列参数并返回一个字节序列。
$ python3 zlib_memory.py
Original : 26 b'This is the original text.'
Compressed : 32 b'789c0bc9c82c5600a2928c5485fca2ccf4ccbcc41c85
92d48a123d007f2f097e'
Decompressed : 26 b'This is the original text.'
前面的示例演示了少量数据的压缩版本可能比未压缩版本大。虽然实际结果取决于输入数据,但观察小数据集的压缩开销很有意思
zlib_lengths.py
import zlib
original_data = b'This is the original text.'
template = '{:>15} {:>15}'
print(template.format('len(data)', 'len(compressed)'))
print(template.format('-' * 15, '-' * 15))
for i in range(5):
data = original_data * i
compressed = zlib.compress(data)
highlight = '*' if len(data) < len(compressed) else ''
print(template.format(len(data), len(compressed)), highlight)
输出中的 *
突出显示压缩数据比未压缩版本占用更多内存的行。
$ python3 zlib_lengths.py
len(data) len(compressed)
--------------- ---------------
0 8 *
26 32 *
52 35
78 35
104 36
zlib
支持几种不同的压缩级别,允许在计算成本和空间减少量之间取得平衡。默认压缩级别 zlib.Z_DEFAULT_COMPRESSION
为 -1
,对应于性能和压缩结果之间折中的硬编码值。这当前对应于级别 6
。
zlib_compresslevel.py
import zlib
input_data = b'Some repeated text.\n' * 1024
template = '{:>5} {:>5}'
print(template.format('Level', 'Size'))
print(template.format('-----', '----'))
for i in range(0, 10):
data = zlib.compress(input_data, i)
print(template.format(i, len(data)))
级别 0 表示根本没有压缩。9 级需要最多的计算并产生最小的输出。如该示例所示,对于给定输入,可以使用多个压缩级别来实现相同的尺寸减小。
$ python3 zlib_compresslevel.py
Level Size
----- ----
0 20491
1 172
2 172
3 172
4 98
5 98
6 98
7 98
8 98
9 98
增量压缩和解压缩
内存中的方法有一些缺点,使得它对于现实世界的用例来说是不切实际的,主要是系统需要足够的内存来同时保存驻留在内存中的未压缩和压缩版本。另一种方法是使用 Compress
和 Decompress
对象来逐步操作数据,这样整个数据集就不必全部纳入内存。
zlib_incremental.py
import zlib
import binascii
compressor = zlib.compressobj(1)
with open('lorem.txt', 'rb') as input:
while True:
block = input.read(64)
if not block:
break
compressed = compressor.compress(block)
if compressed:
print('Compressed: {}'.format(
binascii.hexlify(compressed)))
else:
print('buffering...')
remaining = compressor.flush()
print('Flushed: {}'.format(binascii.hexlify(remaining)))
此示例从纯文本文件中读取小块数据并将其传递给 compress()
。压缩器维护一个压缩数据的内部缓冲区。由于压缩算法取决于校验和和最小块大小,因此压缩器可能无法在每次接收到更多输入时返回数据。如果它没有准备好整个压缩块,则返回一个空字节字符串。当所有数据都被输入时,flush()
方法强制压缩器关闭最后一个块并返回其余的压缩数据。
$ python3 zlib_incremental.py
Compressed: b'7801'
buffering...
buffering...
buffering...
buffering...
buffering...
Flushed: b'55904b6ac4400c44f73e451da0f129b20c2110c85e696b8c40dde
dd167ce1f7915025a087daa9ef4be8c07e4f21c38962e834b800647435fd3b90
747b2810eb9c4bbcc13ac123bded6e4bef1c91ee40d3c6580e3ff52aad2e8cb2
eb6062dad74a89ca904cbb0f2545e0db4b1f2e01955b8c511cb2ac08967d228a
f1447c8ec72e40c4c714116e60cdef171bb6c0feaa255dff1c507c2c4439ec96
05b7e0ba9fc54bae39355cb89fd6ebe5841d673c7b7bc68a46f575a312eebd22
0d4b32441bdc1b36ebf0aedef3d57ea4b26dd986dd39af57dfb05d32279de'
混合内容流
通过 decompressobj()
返回的 Decompress
类也可以使用在压缩和未压缩的数据混合在一起的情况下。
zlib_mixed.py
import zlib
lorem = open('lorem.txt', 'rb').read()
compressed = zlib.compress(lorem)
combined = compressed + lorem
decompressor = zlib.decompressobj()
decompressed = decompressor.decompress(combined)
decompressed_matches = decompressed == lorem
print('Decompressed matches lorem:', decompressed_matches)
unused_matches = decompressor.unused_data == lorem
print('Unused data matches lorem :', unused_matches)
解压缩所有数据后, unused_data
属性包含了未被使用的所有数据。
$ python3 zlib_mixed.py
Decompressed matches lorem: True
Unused data matches lorem : True
校验
除了压缩和解压缩功能外,zlib
还包括两个用于计算数据校验和的函数,adler32()
和 crc32()
。校验和都不是加密安全的,它们仅用于数据完整性验证。
zlib_checksums.py
import zlib
data = open('lorem.txt', 'rb').read()
cksum = zlib.adler32(data)
print('Adler32: {:12d}'.format(cksum))
print(' : {:12d}'.format(zlib.adler32(data, cksum)))
cksum = zlib.crc32(data)
print('CRC-32 : {:12d}'.format(cksum))
print(' : {:12d}'.format(zlib.crc32(data, cksum)))
两个函数都使用相同的参数,一个包含数据的字节字符串和一个可选值,用作校验和的起点。它们返回一个32位有符号整数值,该值也可以作为新的起始点参数在后续调用时传回,以生成 running 校验和。
$ python3 zlib_checksums.py
Adler32: 3542251998
: 669447099
CRC-32 : 3038370516
: 2870078631
压缩网络数据
下一个清单中的服务器使用流压缩器通过将压缩版本的文件写入用于与客户端通信的套接字来响应由文件名组成的请求。
zlib_server.py
import zlib
import logging
import socketserver
import binascii
BLOCK_SIZE = 64
class ZlibRequestHandler(socketserver.BaseRequestHandler):
logger = logging.getLogger('Server')
def handle(self):
compressor = zlib.compressobj(1)
# 找出客户想要的文件
filename = self.request.recv(1024).decode('utf-8')
self.logger.debug('client asked for: %r', filename)
# 在压缩文件时发送文件块
with open(filename, 'rb') as input:
while True:
block = input.read(BLOCK_SIZE)
if not block:
break
self.logger.debug('RAW %r', block)
compressed = compressor.compress(block)
if compressed:
self.logger.debug(
'SENDING %r',
binascii.hexlify(compressed))
self.request.send(compressed)
else:
self.logger.debug('BUFFERING')
# 发送由压缩器缓冲的任何数据
remaining = compressor.flush()
while remaining:
to_send = remaining[:BLOCK_SIZE]
remaining = remaining[BLOCK_SIZE:]
self.logger.debug('FLUSHING %r',
binascii.hexlify(to_send))
self.request.send(to_send)
return
if __name__ == '__main__':
import socket
import threading
from io import BytesIO
logging.basicConfig(
level=logging.DEBUG,
format='%(name)s: %(message)s',
)
logger = logging.getLogger('Client')
# 设置服务器,在单独的线程中运行
address = ('localhost', 0) # 让内核分配一个端口
server = socketserver.TCPServer(address, ZlibRequestHandler)
ip, port = server.server_address # 分配了什么端口?
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True)
t.start()
# 作为客户端连接到服务器
logger.info('Contacting server on %s:%s', ip, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# 索取文件
requested_file = 'lorem.txt'
logger.debug('sending filename: %r', requested_file)
len_sent = s.send(requested_file.encode('utf-8'))
# 接受应答
buffer = BytesIO()
decompressor = zlib.decompressobj()
while True:
response = s.recv(BLOCK_SIZE)
if not response:
break
logger.debug('READ %r', binascii.hexlify(response))
# 在解压器中包含任何未使用的数据。
to_decompress = decompressor.unconsumed_tail + response
while to_decompress:
decompressed = decompressor.decompress(to_decompress)
if decompressed:
logger.debug('DECOMPRESSED %r', decompressed)
buffer.write(decompressed)
# 由于缓冲区溢出,查找未使用的数据
to_decompress = decompressor.unconsumed_tail
else:
logger.debug('BUFFERING')
to_decompress = None
# 处理解压缩缓冲区内剩余的数据
remainder = decompressor.flush()
if remainder:
logger.debug('FLUSHED %r', remainder)
buffer.write(remainder)
full_response = buffer.getvalue()
lorem = open('lorem.txt', 'rb').read()
logger.debug('response matches file contents: %s',
full_response == lorem)
# 清理
s.close()
server.socket.close()
它有一些人工分块来说明当传递给 compress()
或 decompress()
的数据不会导致完整的压缩或未压缩输出块时发生的缓冲行为。
客户端连接到套接字并请求文件。然后它循环,接收压缩数据块。由于块可能不包含足以完全解压缩的信息,因此先前接收的任何数据的剩余部分将与新数据组合并传递给解压缩器。在解压缩数据时,将其附加到缓冲区,该缓冲区与处理循环结束时的文件内容进行比较。
警告
此服务器具有明显的安全隐患。不要在开放网络的系统上或安全性可能存在问题的任何环境中运行它。
$ python3 zlib_server.py
Client: Contacting server on 127.0.0.1:53658
Client: sending filename: 'lorem.txt'
Server: client asked for: 'lorem.txt'
Server: RAW b'Lorem ipsum dolor sit amet, consectetuer adipiscin
g elit. Donec\n'
Server: SENDING b'7801'
Server: RAW b'egestas, enim et consectetuer ullamcorper, lectus
ligula rutrum '
Server: BUFFERING
Server: RAW b'leo, a\nelementum elit tortor eu quam. Duis tincid
unt nisi ut ant'
Server: BUFFERING
Server: RAW b'e. Nulla\nfacilisi. Sed tristique eros eu libero.
Pellentesque ve'
Server: BUFFERING
Server: RAW b'l arcu. Vivamus\npurus orci, iaculis ac, suscipit
sit amet, pulvi'
Client: READ b'7801'
Client: BUFFERING
Server: BUFFERING
Server: RAW b'nar eu,\nlacus.\n'
Server: BUFFERING
Server: FLUSHING b'55904b6ac4400c44f73e451da0f129b20c2110c85e696
b8c40ddedd167ce1f7915025a087daa9ef4be8c07e4f21c38962e834b8006474
35fd3b90747b2810eb9'
Server: FLUSHING b'c4bbcc13ac123bded6e4bef1c91ee40d3c6580e3ff52a
ad2e8cb2eb6062dad74a89ca904cbb0f2545e0db4b1f2e01955b8c511cb2ac08
967d228af1447c8ec72'
Client: READ b'55904b6ac4400c44f73e451da0f129b20c2110c85e696b8c4
0ddedd167ce1f7915025a087daa9ef4be8c07e4f21c38962e834b800647435fd
3b90747b2810eb9'
Server: FLUSHING b'e40c4c714116e60cdef171bb6c0feaa255dff1c507c2c
4439ec9605b7e0ba9fc54bae39355cb89fd6ebe5841d673c7b7bc68a46f575a3
12eebd220d4b32441bd'
Client: DECOMPRESSED b'Lorem ipsum dolor sit amet, consectetuer
adi'
Client: READ b'c4bbcc13ac123bded6e4bef1c91ee40d3c6580e3ff52aad2e
8cb2eb6062dad74a89ca904cbb0f2545e0db4b1f2e01955b8c511cb2ac08967d
228af1447c8ec72'
Client: DECOMPRESSED b'piscing elit. Donec\negestas, enim et con
sectetuer ullamcorper, lectus ligula rutrum leo, a\nelementum el
it tortor eu quam. Duis tinci'
Client: READ b'e40c4c714116e60cdef171bb6c0feaa255dff1c507c2c4439
ec9605b7e0ba9fc54bae39355cb89fd6ebe5841d673c7b7bc68a46f575a312ee
bd220d4b32441bd'
Client: DECOMPRESSED b'dunt nisi ut ante. Nulla\nfacilisi. Sed t
ristique eros eu libero. Pellentesque vel arcu. Vivamus\npurus o
rci, iaculis ac'
Server: FLUSHING b'c1b36ebf0aedef3d57ea4b26dd986dd39af57dfb05d32
279de'
Client: READ b'c1b36ebf0aedef3d57ea4b26dd986dd39af57dfb05d32279d
e'
Client: DECOMPRESSED b', suscipit sit amet, pulvinar eu,\nlacus.
\n'
Client: response matches file contents: True
也可以看看
- zlib 标准库文档
gzip
--gzip
模块包含 zlib 库的更高级别(基于文件)接口。- www.zlib.net/ -- zlib 库的主页。
- www.zlib.net/manual.html -- 完整的zlib文档。
bz2
--bz2
模块提供与 bzip2 压缩库类似的接口。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。