欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python3标准库:zlib GNUzlib压缩

程序员文章站 2022-12-20 16:35:47
1. zlib GNUzlib压缩 zlib模块为GNU项目zlib压缩库中的很多函数提供了底层接口。 1.1 处理内存中的数据 使用zlib最简单的方法要求把所有将要压缩或解压缩的数据存放在内存中。 import zlib import binascii original_data = b'Thi ......

1. zlib gnuzlib压缩

zlib模块为gnu项目zlib压缩库中的很多函数提供了底层接口。

1.1 处理内存中的数据

使用zlib最简单的方法要求把所有将要压缩或解压缩的数据存放在内存中。

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 GNUzlib压缩

从前面的例子可以看到,少量数据的压缩版本可能比未压缩的版本还要大。具体的结果取决于输入数据,不过观察小数据集的压缩开销很有意思。 

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 GNUzlib压缩

zlib支持不同的压缩级别,允许在计算成本和空间缩减量之间有所平衡。默认压缩级别zlib.z_default_compression为-1,这对应一个硬编码值,表示性能和压缩结果之间的一个折中。当前这对应级别6。

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 GNUzlib压缩

1.2 增量压缩与解压缩

这种内存中的压缩方法有一些缺点,主要是系统需要有足够的内存,可以在内存中同时驻留未压缩和压缩版本,因此这种方法对于真实世界的用例并不实用。另一种方法是使用compress和decompress对象以增量方式处理数据,这样就不需要将整个数据集都放在内存中。

import zlibimport 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()。压缩器维护压缩数据的一个内存缓冲区。由于压缩算法依赖于校验和以及最小块大小,所以压缩器每次接收更多输入时可能并没有准备好返回数据。如果它没有准备好一个完整的压缩块,那便会返回一个空字节串。当所有

Python3标准库:zlib GNUzlib压缩

1.3 混合内容流

在压缩和未压缩数据混合在一起的情况下,还可以使用decompressobj()返回的decompress类。

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 GNUzlib压缩

1.4 校验和

除了压缩和解压缩函数,zlib还包括两个用于计算数据的校验和的函数,分别是adler32()和crc32()。这两个函数计算出的校验和都不能认为是密码安全的,它们只用于数据完整性验证。

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位有符号整数值,这个值可以作为一个新的起点参数再传回给后续的调用,以生成一个动态变化的校验和。

Python3标准库:zlib GNUzlib压缩

1.5 压缩网络数据

下一个代码清单中的服务器使用流压缩器来响应文件名请求,它将文件的一个压缩版本写至与客户通信的套接字中。

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)

        # find out what file the client wants
        filename = self.request.recv(1024).decode('utf-8')
        self.logger.debug('client asked for: %r', filename)

        # send chunks of the file as they are compressed
        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')

        # send any data being buffered by the compressor
        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')

    # set up a server, running in a separate thread
    address = ('localhost', 0)  # let the kernel assign a port
    server = socketserver.tcpserver(address, zlibrequesthandler)
    ip, port = server.server_address  # what port was assigned?

    t = threading.thread(target=server.serve_forever)
    t.setdaemon(true)
    t.start()

    # connect to the server as a client
    logger.info('contacting server on %s:%s', ip, port)
    s = socket.socket(socket.af_inet, socket.sock_stream)
    s.connect((ip, port))

    # ask for a file
    requested_file = 'lorem.txt'
    logger.debug('sending filename: %r', requested_file)
    len_sent = s.send(requested_file.encode('utf-8'))

    # receive a response
    buffer = bytesio()
    decompressor = zlib.decompressobj()
    while true:
        response = s.recv(block_size)
        if not response:
            break
        logger.debug('read %r', binascii.hexlify(response))

        # include any unconsumed data when
        # feeding the decompressor.
        to_decompress = decompressor.unconsumed_tail + response
        while to_decompress:
            decompressed = decompressor.decompress(to_decompress)
            if decompressed:
                logger.debug('decompressed %r', decompressed)
                buffer.write(decompressed)
                # look for unconsumed data due to buffer overflow
                to_decompress = decompressor.unconsumed_tail
            else:
                logger.debug('buffering')
                to_decompress = none

    # deal with data reamining inside the decompressor buffer
    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)

    # clean up
    s.close()
    server.socket.close()

我们人为的将这个代码清单做了一些划分,以展示缓冲行为,如果将数据传递到compress()或decompress(),但没有得到完整的压缩或未压缩输出块,此时便会进行缓冲。

客户连接到套接字,并请求一个文件。然后循环,接收压缩数据块。由于一个块可能未包含足够多的信息来完全解压缩,所以之前接收的剩余数据将与新数据结合,并且传递到解压缩器。解压缩数据时,会把它追加到一个缓冲区,处理循环结束时将与文件内容进行比较。

Python3标准库:zlib GNUzlib压缩