zoukankan      html  css  js  c++  java
  • Python3标准库:zlib GNUzlib压缩

    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()函数都取一个字节序列参数,并且返回一个字节序列。

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

    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)

    输出中的*突出显示了哪些行的压缩数据比未压缩版本占用的内存更多。

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

    import zlib
    
    input_data = b'Some repeated text.
    ' * 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要求的计算最多,同时会生成最小的输出。如下面的例子,对于一个给定的输入,可以多个压缩级别得到的空间缩减量是一样的。

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

    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属性会包含未用的所有数据。

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

    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(),但没有得到完整的压缩或未压缩输出块,此时便会进行缓冲。

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

  • 相关阅读:
    java学习day35-三大框架-MyBatis(一)
    java学习day35-数据库事务
    java学习day34-Cookie和Session的区别
    java学习day34-Session技术
    Hadoop Java Versions
    如何下载和安装预构建的OpenJDK软件包
    How to install and configure samba on RHEL 8 / CentOS 8
    Win10系统下安装的Office版本的说明和注意事项
    Apache Hadoop
    nmap常用命令整理(长期更新)
  • 原文地址:https://www.cnblogs.com/liuhui0308/p/12583668.html
Copyright © 2011-2022 走看看