Автор оригинала: Doug Hellmann.
Цель:
Низкоуровневый доступ к библиотеке сжатия GNU zlib
Модуль zlib
предоставляет интерфейс нижнего уровня для многих функций в библиотеке сжатия zlib
из проекта GNU.
Работа с данными в памяти
Самый простой способ работы с 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 ()
и decopress ()
принимают аргумент байтовой последовательности и возвращают байтовую последовательность.
$ 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'
Потоки смешанного контента
Класс Decompress
, возвращаемый функцией decopressobj ()
, также можно использовать в ситуациях, когда сжатые и несжатые данные смешиваются вместе.
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-битное целое число со знаком, которое также может быть передано при последующих вызовах в качестве нового аргумента начальной точки для получения текущей контрольной суммы.
$ 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) # 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( levellogging.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(targetserver.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 ()
или decopress ()
, не приводят к созданию полного блока сжатого или несжатый вывод.
Клиент подключается к сокету и запрашивает файл. Затем он зацикливается, получая блоки сжатых данных. Поскольку блок может не содержать достаточно информации, чтобы полностью его распаковать, остаток любых данных, полученных ранее, объединяется с новыми данными и передается в распаковщик. Когда данные распаковываются, они добавляются в буфер, который сравнивается с содержимым файла в конце цикла обработки.
Предупреждение
Этот сервер имеет очевидные последствия для безопасности. Не запускайте его в системе в открытом Интернете или в любой среде, где безопасность может быть проблемой.
$ 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. - http://www.zlib.net/ – домашняя страница библиотеки zlib.
- http://www.zlib.net/manual.html – полная документация по zlib.
- bz2 – модуль
bz2
предоставляет интерфейс, аналогичный библиотеке сжатия bzip2.