Рубрики
Без рубрики

bz2 – bzip2 Сжатие

Автор оригинала: Doug Hellmann.

Цель:

bzip2 сжатие

Модуль bz2 – это интерфейс для библиотеки bzip2, используемый для сжатия данных для хранения или передачи. Предоставляется три API:

  • “одноразовые” функции сжатия/декомпрессии для работы с большим объемом данных
  • объекты итеративного сжатия/распаковки для работы с потоками данных
  • файловый класс, который поддерживает чтение и запись как несжатый файл

Одноразовые операции в памяти

Самый простой способ работать с bz2 – загрузить все данные для сжатия или распаковки в память, а затем использовать compress () и decopress () , чтобы преобразовать его.

bz2_memory.py

import bz2
import binascii

original_data  b'This is the original text.'
print('Original     : {} bytes'.format(len(original_data)))
print(original_data)

print()
compressed  bz2.compress(original_data)
print('Compressed   : {} bytes'.format(len(compressed)))
hex_version  binascii.hexlify(compressed)
for i in range(len(hex_version) // 40 + 1):
    print(hex_version[i * 40:(i + 1) * 40])

print()
decompressed  bz2.decompress(compressed)
print('Decompressed : {} bytes'.format(len(decompressed)))
print(decompressed)

Сжатые данные содержат символы, отличные от ASCII, поэтому перед печатью их необходимо преобразовать в шестнадцатеричное представление. В выходных данных этих примеров шестнадцатеричная версия переформатирована так, чтобы в каждой строке было не более 40 символов.

$ python3 bz2_memory.py

Original     : 26 bytes
b'This is the original text.'

Compressed   : 62 bytes
b'425a683931415926535916be35a6000002938040'
b'01040022e59c402000314c000111e93d434da223'
b'028cf9e73148cae0a0d6ed7f17724538509016be'
b'35a6'

Decompressed : 26 bytes
b'This is the original text.'

Для короткого текста сжатая версия может быть значительно длиннее оригинала. Хотя фактические результаты зависят от входных данных, интересно наблюдать накладные расходы на сжатие.

bz2_lengths.py

import bz2

original_data  b'This is the original text.'

fmt  '{:>15}  {:>15}'
print(fmt.format('len(data)', 'len(compressed)'))
print(fmt.format('-' * 15, '-' * 15))

for i in range(5):
    data  original_data * i
    compressed  bz2.compress(data)
    print(fmt.format(len(data), len(compressed)), end'')
    print('*' if len(data) < len(compressed) else '')

Строки вывода, заканчивающиеся на * , показывают точки, в которых сжатые данные длиннее, чем исходные данные.

$ python3 bz2_lengths.py

      len(data)  len(compressed)
---------------  ---------------
              0               14*
             26               62*
             52               68*
             78               70
            104               72

Инкрементное сжатие и декомпрессия

Подход в памяти имеет очевидные недостатки, которые делают его непрактичным для реальных случаев использования. Альтернативой является использование объектов BZ2Compressor и BZ2Decompressor для постепенного управления данными, так что весь набор данных не должен помещаться в память.

bz2_incremental.py

import bz2
import binascii
import io

compressor  bz2.BZ2Compressor()

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 bz2_incremental.py

buffering...
buffering...
buffering...
buffering...
Flushed: b'425a6839314159265359ba83a48c000014d5800010400504052fa
7fe003000ba9112793d4ca789068698a0d1a341901a0d53f4d1119a8d4c9e812
d755a67c10798387682c7ca7b5a3bb75da77755eb81c1cb1ca94c4b6faf209c5
2a90aaa4d16a4a1b9c167a01c8d9ef32589d831e77df7a5753a398b11660e392
126fc18a72a1088716cc8dedda5d489da410748531278043d70a8a131c2b8adc
d6a221bdb8c7ff76b88c1d5342ee48a70a12175074918'

Потоки смешанного контента

BZ2Decompressor также можно использовать в ситуациях, когда сжатые и несжатые данные смешиваются вместе.

bz2_mixed.py

import bz2

lorem  open('lorem.txt', 'rt').read().encode('utf-8')
compressed  bz2.compress(lorem)
combined  compressed + lorem

decompressor  bz2.BZ2Decompressor()
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 bz2_mixed.py

Decompressed matches lorem: True
Unused data matches lorem : True

Запись сжатых файлов

BZ2File можно использовать для записи и чтения из файлов, сжатых с помощью bzip2, с использованием обычных методов записи и чтения данных.

bz2_file_write.py

import bz2
import io
import os

data  'Contents of the example file go here.\n'

with bz2.BZ2File('example.bz2', 'wb') as output:
    with io.TextIOWrapper(output, encoding'utf-8') as enc:
        enc.write(data)

os.system('file example.bz2')

Чтобы записать данные в сжатый файл, откройте файл в режиме 'wb' . В этом примере BZ2File заключен в оболочку TextIOWrapper из модуля io для кодирования текста Unicode в байты, подходящие для сжатия.

$ python3 bz2_file_write.py

example.bz2: bzip2 compressed data, block size = 900k

Можно использовать разные уровни сжатия, передав аргумент compresslevel . Допустимые значения находятся в диапазоне от 1 до 9 включительно. Более низкие значения быстрее и приводят к меньшему сжатию. Более высокие значения медленнее и сжимаются сильнее, вплоть до определенного момента.

bz2_file_compresslevel.py

import bz2
import io
import os

data  open('lorem.txt', 'r', encoding'utf-8').read() * 1024
print('Input contains {} bytes'.format(
    len(data.encode('utf-8'))))

for i in range(1, 10):
    filename  'compress-level-{}.bz2'.format(i)
    with bz2.BZ2File(filename, 'wb', compressleveli) as output:
        with io.TextIOWrapper(output, encoding'utf-8') as enc:
            enc.write(data)
    os.system('cksum {}'.format(filename))

Центральный столбец чисел в выводе сценария – это размер в байтах созданных файлов. Для этих входных данных более высокие значения сжатия не всегда окупаются уменьшением объема памяти для тех же входных данных. Результаты будут отличаться для других входов.

$ python3 bz2_file_compresslevel.py

3018243926 8771 compress-level-1.bz2
1942389165 4949 compress-level-2.bz2
2596054176 3708 compress-level-3.bz2
1491394456 2705 compress-level-4.bz2
1425874420 2705 compress-level-5.bz2
2232840816 2574 compress-level-6.bz2
447681641 2394 compress-level-7.bz2
3699654768 1137 compress-level-8.bz2
3103658384 1137 compress-level-9.bz2
Input contains 754688 bytes

Экземпляр BZ2File также включает метод writelines () , который можно использовать для записи последовательности строк.

bz2_file_writelines.py

import bz2
import io
import itertools
import os

data  'The same line, over and over.\n'

with bz2.BZ2File('lines.bz2', 'wb') as output:
    with io.TextIOWrapper(output, encoding'utf-8') as enc:
        enc.writelines(itertools.repeat(data, 10))

os.system('bzcat lines.bz2')

Строки должны заканчиваться символом новой строки, как при записи в обычный файл.

$ python3 bz2_file_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' ). Значение, возвращаемое функцией read () , будет байтовой строкой.

bz2_file_read.py

import bz2
import io

with bz2.BZ2File('example.bz2', 'rb') as input:
    with io.TextIOWrapper(input, encoding'utf-8') as dec:
        print(dec.read())

В этом примере выполняется чтение файла, записанного bz2_file_write.py из предыдущего раздела. BZ2File заключен в оболочку TextIOWrapper для декодирования байтов, прочитанных в текст Unicode.

$ python3 bz2_file_read.py

Contents of the example file go here.

При чтении файла также можно искать и читать только часть данных.

bz2_file_seek.py

import bz2
import contextlib

with bz2.BZ2File('example.bz2', 'rb') as input:
    print('Entire file:')
    all_data  input.read()
    print(all_data)

    expected  all_data[5:15]

    # rewind to beginning
    input.seek(0)

    # move ahead 5 bytes
    input.seek(5)
    print('Starting at position 5 for 10 bytes:')
    partial  input.read(10)
    print(partial)

    print()
    print(expected  partial)

Положение seek () относится к несжатым данным, поэтому вызывающему абоненту не нужно знать, что файл данных сжат. Это позволяет передать экземпляр BZ2File функции, ожидающей обычный несжатый файл.

$ python3 bz2_file_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

Чтение и запись данных Unicode

В предыдущих примерах непосредственно использовался BZ2File и управлялось кодирование и декодирование текстовых строк Unicode, встроенных с помощью io.TextIOWrapper , где это необходимо. Этих дополнительных действий можно избежать, используя bz2.open () , который настраивает io.TextIOWrapper для автоматической обработки кодирования или декодирования.

bz2_unicode.py

import bz2
import os

data  'Character with an åccent.'

with bz2.open('example.bz2', 'wt', encoding'utf-8') as output:
    output.write(data)

with bz2.open('example.bz2', 'rt', encoding'utf-8') as input:
    print('Full file: {}'.format(input.read()))

# Move to the beginning of the accented character.
with bz2.open('example.bz2', 'rt', encoding'utf-8') as input:
    input.seek(18)
    print('One character: {}'.format(input.read(1)))

# Move to the middle of the accented character.
with bz2.open('example.bz2', 'rt', encoding'utf-8') as input:
    input.seek(19)
    try:
        print(input.read(1))
    except UnicodeDecodeError:
        print('ERROR: failed to decode')

Дескриптор файла, возвращаемый open () , поддерживает seek () , но будьте осторожны, потому что указатель файла перемещается на байты , а не на символы и может оказаться в середине закодированного символа.

$ python3 bz2_unicode.py

Full file: Character with an åccent.
One character: å
ERROR: failed to decode

Сжатие сетевых данных

Код в следующем примере отвечает на запросы, состоящие из имен файлов, записывая сжатую версию файла в сокет, используемый для связи с клиентом. В нем есть несколько искусственных фрагментов, чтобы проиллюстрировать буферизацию, которая происходит, когда данные, переданные в compress () или decopress () , не приводят к созданию полного блока сжатых или несжатых файлов. выход.

bz2_server.py

import bz2
import logging
import socketserver
import binascii

BLOCK_SIZE  32


class Bz2RequestHandler(socketserver.BaseRequestHandler):

    logger  logging.getLogger('Server')

    def handle(self):
        compressor  bz2.BZ2Compressor()

        # Find out what file the client wants
        filename  self.request.recv(1024).decode('utf-8')
        self.logger.debug('client asked for: "%s"', 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

Основная программа запускает сервер в потоке, объединяющем SocketServer и Bz2RequestHandler .

if __name__  '__main__':
    import socket
    import sys
    from io import StringIO
    import threading

    logging.basicConfig(levellogging.DEBUG,
                        format'%(name)s: %(message)s',
                        )

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

    t  threading.Thread(targetserver.serve_forever)
    t.setDaemon(True)
    t.start()

    logger  logging.getLogger('Client')

    # Connect to the server
    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  (sys.argv[0]
                      if len(sys.argv) > 1
                      else 'lorem.txt')
    logger.debug('sending filename: "%s"', requested_file)
    len_sent  s.send(requested_file.encode('utf-8'))

    # Receive a response
    buffer  StringIO()
    decompressor  bz2.BZ2Decompressor()
    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.
        decompressed  decompressor.decompress(response)
        if decompressed:
            logger.debug('DECOMPRESSED %r', decompressed)
            buffer.write(decompressed.decode('utf-8'))
        else:
            logger.debug('BUFFERING')

    full_response  buffer.getvalue()
    lorem  open(requested_file, 'rt').read()
    logger.debug('response matches file contents: %s',
                 full_response  lorem)

    # Clean up
    server.shutdown()
    server.socket.close()
    s.close()

Затем он открывает сокет для связи с сервером в качестве клиента и запрашивает файл (по умолчанию – lorem.txt ), который содержит:

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo,
a elementum elit tortor eu quam. Duis tincidunt nisi ut ante. Nulla
facilisi.

Предупреждение

Эта реализация имеет очевидные последствия для безопасности. Не запускайте его на сервере в открытом Интернете или в любой среде, где безопасность может быть проблемой.

Запуск bz2_server.py производит:

$ python3 bz2_server.py

Client: Contacting server on 127.0.0.1:57364
Client: sending filename: "lorem.txt"
Server: client asked for: "lorem.txt"
Server: RAW b'Lorem ipsum dolor sit amet, cons'
Server: BUFFERING
Server: RAW b'ectetuer adipiscing elit. Donec\n'
Server: BUFFERING
Server: RAW b'egestas, enim et consectetuer ul'
Server: BUFFERING
Server: RAW b'lamcorper, lectus ligula rutrum '
Server: BUFFERING
Server: RAW b'leo,\na elementum elit tortor eu '
Server: BUFFERING
Server: RAW b'quam. Duis tincidunt nisi ut ant'
Server: BUFFERING
Server: RAW b'e. Nulla\nfacilisi.\n'
Server: BUFFERING
Server: FLUSHING b'425a6839314159265359ba83a48c000014d5800010400
504052fa7fe003000ba'
Server: FLUSHING b'9112793d4ca789068698a0d1a341901a0d53f4d1119a8
d4c9e812d755a67c107'
Client: READ b'425a6839314159265359ba83a48c000014d58000104005040
52fa7fe003000ba'
Server: FLUSHING b'98387682c7ca7b5a3bb75da77755eb81c1cb1ca94c4b6
faf209c52a90aaa4d16'
Client: BUFFERING
Server: FLUSHING b'a4a1b9c167a01c8d9ef32589d831e77df7a5753a398b1
1660e392126fc18a72a'
Client: READ b'9112793d4ca789068698a0d1a341901a0d53f4d1119a8d4c9
e812d755a67c107'
Server: FLUSHING b'1088716cc8dedda5d489da410748531278043d70a8a13
1c2b8adcd6a221bdb8c'
Client: BUFFERING
Server: FLUSHING b'7ff76b88c1d5342ee48a70a12175074918'
Client: READ b'98387682c7ca7b5a3bb75da77755eb81c1cb1ca94c4b6faf2
09c52a90aaa4d16'
Client: BUFFERING
Client: READ b'a4a1b9c167a01c8d9ef32589d831e77df7a5753a398b11660
e392126fc18a72a'
Client: BUFFERING
Client: READ b'1088716cc8dedda5d489da410748531278043d70a8a131c2b
8adcd6a221bdb8c'
Client: BUFFERING
Client: READ b'7ff76b88c1d5342ee48a70a12175074918'
Client: DECOMPRESSED b'Lorem ipsum dolor sit amet, consectetuer
adipiscing elit. Donec\negestas, enim et consectetuer ullamcorpe
r, lectus ligula rutrum leo,\na elementum elit tortor eu quam. D
uis tincidunt nisi ut ante. Nulla\nfacilisi.\n'
Client: response matches file contents: True

Смотрите также