zlib – GNU zlib 圧縮ライブラリへの低レベルアクセス¶
目的: | GNU zlib 圧縮ライブラリへの低レベルアクセス |
---|---|
利用できるバージョン: | 2.5 以上 |
zlib モジュールは GNU zlib 圧縮ライブラリ関数群への低レベルなインタフェースを提供します。
メモリ内のデータを扱う¶
zlib を使用する最も簡単な方法はメモリ内に全データを圧縮したり解凍したりするように要求することです。そうするには compress() や decompress() を使用します。
import zlib
import binascii
original_data = '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
$ python zlib_memory.py
Original : 26 This is the original text.
Compressed : 32 789c0bc9c82c5600a2928c5485fca2ccf4ccbcc41c8592d48a123d007f2f097e
Decompressed : 26 This is the original text.
短いテキストを圧縮すると逆に長くなる可能性があることに注意してください。その実際の結果は入力値次第であるとはいえ、短いテキストで圧縮によるオーバヘッドを観察することはおもしろいです。
import zlib
original_data = 'This is the original text.'
fmt = '%15s %15s'
print fmt % ('len(data)', 'len(compressed)')
print fmt % ('-' * 15, '-' * 15)
for i in xrange(20):
data = original_data * i
compressed = zlib.compress(data)
print fmt % (len(data), len(compressed)), '*' if len(data) < len(compressed) else ''
$ python zlib_lengths.py
len(data) len(compressed)
--------------- ---------------
0 8 *
26 32 *
52 35
78 35
104 36
130 36
156 36
182 36
208 36
234 36
260 36
286 36
312 37
338 37
364 38
390 38
416 38
442 38
468 38
494 38
ストリームを扱う¶
インメモリアプローチは現実の世界では実用的ではないという明らかな欠点があります。完全なデータセットをメモり内に読み込む必要がないように、代替手段としてデータのストリームを扱う Compress と Decompress オブジェクトを使用します。
次のシンプルサーバはファイル名を含むリクエストに対して、クライアントとの通信ソケットへそのファイルの圧縮版を書き込むことでレスポンスを返します。 compress() 又は decompress() へ渡されたデータに対して圧縮又は解凍した出力が完全なブロックではない場合に発生するバッファリングの動作を説明するために適当な位置に人為的なチャンクがあります。
Warning
このサーバは明らかにセキュリティに問題があります。インターネットやセキュリティが問題となる環境のシステムでこのサーバを実行してはいけません。
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)
self.logger.debug('client asked for: "%s"', filename)
# 圧縮されるようにファイルのチャンクを送信する
with open(filename, 'rb') as input:
while True:
block = input.read(BLOCK_SIZE)
if not block:
break
self.logger.debug('RAW "%s"', block)
compressed = compressor.compress(block)
if compressed:
self.logger.debug('SENDING "%s"', binascii.hexlify(compressed))
self.request.send(compressed)
else:
self.logger.debug('BUFFERING')
# compressor でバッファされるデータを送信する
remaining = compressor.flush()
while remaining:
to_send = remaining[:BLOCK_SIZE]
remaining = remaining[BLOCK_SIZE:]
self.logger.debug('FLUSHING "%s"', binascii.hexlify(to_send))
self.request.send(to_send)
return
if __name__ == '__main__':
import socket
import threading
from cStringIO import StringIO
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: "%s"', requested_file)
len_sent = s.send(requested_file)
# レスポンスを受け取る
buffer = StringIO()
decompressor = zlib.decompressobj()
while True:
response = s.recv(BLOCK_SIZE)
if not response:
break
logger.debug('READ "%s"', binascii.hexlify(response))
# decompressor に入力する消費されていないデータを含める
to_decompress = decompressor.unconsumed_tail + response
while to_decompress:
decompressed = decompressor.decompress(to_decompress)
if decompressed:
logger.debug('DECOMPRESSED "%s"', decompressed)
buffer.write(decompressed)
# バッファオーバーフローに起因して消費されていないデータを探す
to_decompress = decompressor.unconsumed_tail
else:
logger.debug('BUFFERING')
to_decompress = None
# decompressor バッファ内部に残っているデータを扱う
remainder = decompressor.flush()
if remainder:
logger.debug('FLUSHED "%s"', remainder)
buffer.write(reaminder)
full_response = buffer.getvalue()
lorem = open('lorem.txt', 'rt').read()
logger.debug('response matches file contents: %s', full_response == lorem)
# クリーンアップ
s.close()
server.socket.close()
$ python zlib_server.py
Client: Contacting server on 127.0.0.1:59624
Client: sending filename: "lorem.txt"
Server: client asked for: "lorem.txt"
Server: RAW "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
"
Server: SENDING "7801"
Server: RAW "egestas, enim et consectetuer ullamcorper, lectus ligula rutrum "
Server: BUFFERING
Server: RAW "leo, a
elementum elit tortor eu quam. Duis tincidunt nisi ut ant"
Server: BUFFERING
Server: RAW "e. Nulla
facilisi. Sed tristique eros eu libero. Pellentesque ve"
Server: BUFFERING
Server: RAW "l arcu. Vivamus
purus orci, iaculis ac, suscipit sit amet, pulvi"
Client: READ "7801"
Server: BUFFERING
Client: BUFFERING
Server: RAW "nar eu,
lacus. Praesent placerat tortor sed nisl. Nunc blandit d"
Server: BUFFERING
Server: RAW "iam egestas
dui. Pellentesque habitant morbi tristique senectus "
Server: BUFFERING
Server: RAW "et netus et
malesuada fames ac turpis egestas. Aliquam viverra f"
Server: BUFFERING
Server: RAW "ringilla
leo. Nulla feugiat augue eleifend nulla. Vivamus mauris"
Server: BUFFERING
Server: RAW ". Vivamus sed
mauris in nibh placerat egestas. Suspendisse poten"
Server: BUFFERING
Server: RAW "ti. Mauris massa. Ut
eget velit auctor tortor blandit sollicitud"
Server: BUFFERING
Server: RAW "in. Suspendisse imperdiet
justo.
"
Server: BUFFERING
Server: FLUSHING "5592418edb300c45f73e050f60f80e05ba6c8b0245bb676426c382923c22e9f3f70bc94c1ac00b9b963eff7fe4b73ea4921e9e95f66e7d906b105789954a6f2e"
Server: FLUSHING "25245206f1ae877ad17623318d8dbef62665919b78b0af244d2b49bc5e4a33aea58f43c64a06ad7432bda5318d8c819e267d255ec4a44a0b14a638451f784892"
Server: FLUSHING "de932b7aa53a85b6a27bb6a0a6ae94b0d94236fa31bb2c572e6aa86ff44b768aa11efa9e4232ba4f21d30b5e37fa2966e8243e7f9e62c4a3e4467ff4e49abe1c"
Client: READ "5592418edb300c45f73e050f60f80e05ba6c8b0245bb676426c382923c22e9f3f70bc94c1ac00b9b963eff7fe4b73ea4921e9e95f66e7d906b105789954a6f2e"
Server: FLUSHING "39e0b18fa22b299784247159c913d90f587be239d24e6d3c6dae8be1ac437db038e4e94041067f467198826d9b765ba18b71dba1b62b23f29de1b227dcbff87b"
Client: DECOMPRESSED "Lorem ipsum dolor sit amet, conse"
Server: FLUSHING "e38b065252ede3a2ffa5428f3b4d106f181022c652d9c49377a62b06387d53e4c0d43e3a6cf4c500052d4f3d650c1c1c18a84e7e18c403255d256f0aeb9cb709"
Client: READ "25245206f1ae877ad17623318d8dbef62665919b78b0af244d2b49bc5e4a33aea58f43c64a06ad7432bda5318d8c819e267d255ec4a44a0b14a638451f784892"
Server: FLUSHING "d044afd2607f72fe24459513909fdf480807b346da90f5f2f684f04888d9a41fd05277a1a3074821f2f7fbadcaeed0ff1d73a962ce666e6296b9098f85f8c0e6"
Client: DECOMPRESSED "ctetuer adipiscing elit. Donec
egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo, a
elementum elit tortor eu"
Server: FLUSHING "dd4c8b46eeda5e45b562d776058dbfe9d1b7e51f6f370ea5"
Client: READ "de932b7aa53a85b6a27bb6a0a6ae94b0d94236fa31bb2c572e6aa86ff44b768aa11efa9e4232ba4f21d30b5e37fa2966e8243e7f9e62c4a3e4467ff4e49abe1c"
Client: DECOMPRESSED " quam. Duis tincidunt nisi ut ante. Nulla
facilisi. Sed tristique eros eu libero. Pellentesque vel arcu. Vivamus
p"
Client: READ "39e0b18fa22b299784247159c913d90f587be239d24e6d3c6dae8be1ac437db038e4e94041067f467198826d9b765ba18b71dba1b62b23f29de1b227dcbff87b"
Client: DECOMPRESSED "urus orci, iaculis ac, suscipit sit amet, pulvinar eu,
lacus. Praesent placerat tortor sed nisl. Nunc blandit diam egestas
dui. Pellentesque "
Client: READ "e38b065252ede3a2ffa5428f3b4d106f181022c652d9c49377a62b06387d53e4c0d43e3a6cf4c500052d4f3d650c1c1c18a84e7e18c403255d256f0aeb9cb709"
Client: DECOMPRESSED "habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Aliquam viverra fringilla
leo. Nulla feugiat aug"
Client: READ "d044afd2607f72fe24459513909fdf480807b346da90f5f2f684f04888d9a41fd05277a1a3074821f2f7fbadcaeed0ff1d73a962ce666e6296b9098f85f8c0e6"
Client: DECOMPRESSED "ue eleifend nulla. Vivamus mauris. Vivamus sed
mauris in nibh placerat egestas. Suspendisse potenti. Mauris massa. Ut
eget velit auctor tortor blandit s"
Client: READ "dd4c8b46eeda5e45b562d776058dbfe9d1b7e51f6f370ea5"
Client: DECOMPRESSED "ollicitudin. Suspendisse imperdiet
justo.
"
Client: response matches file contents: True
混在したコンテキストストリーム¶
decompressobj() が返す Decompress クラスは圧縮と非圧縮のデータが混在した状況でも使用することができます。そのデータを完全に解凍した後でその unused_data 属性に使用しなかったデータがあります。
import zlib
lorem = open('lorem.txt', 'rt').read()
compressed = zlib.compress(lorem)
combined = compressed + lorem
decompressor = zlib.decompressobj()
decompressed = decompressor.decompress(combined)
print 'Decompressed matches lorem:', decompressed == lorem
print 'Unused data matches lorem :', decompressor.unused_data == lorem
$ python zlib_mixed.py
Decompressed matches lorem: True
Unused data matches lorem : True
チェックサム¶
圧縮や解凍の関数に加えて zlib はデータのチェックサムを算出するための adler32() と crc32() という2つの関数があります。チェックサムは暗号化によるセキュリティではなく、データの整合性検証の用途のみに使用されます。
2つの関数は同じ引数を取ります。その引数はデータの文字列とそのチェックサムの開始地点として使用されるオプションの値です。その2つの関数は 実行 チェックサムを生成するために新たな開始地点として、その次に続く関数呼び出しへ渡すこともできる符号付き 32 bit 整数値を返します。
import zlib
data = open('lorem.txt', 'r').read()
cksum = zlib.adler32(data)
print 'Adler32: %12d' % cksum
print ' : %12d' % zlib.adler32(data, cksum)
cksum = zlib.crc32(data)
print 'CRC-32 : %12d' % cksum
print ' : %12d' % zlib.crc32(data, cksum)
$ python zlib_checksums.py
Adler32: 1865879205
: 118955337
CRC-32 : 1878123957
: -1940264325
Adler32 アルゴリズムは標準 CRC よりも速いと言われていますが、私のテストでは遅くなることを発見しました。
import timeit
iterations = 1000
def show_results(title, result, iterations):
"Print results in terms of microseconds per pass and per item."
per_pass = 1000000 * (result / iterations)
print '%s:\t%.2f usec/pass' % (title, per_pass)
adler32 = timeit.Timer(
stmt="zlib.adler32(data)",
setup="import zlib; data=open('lorem.txt','r').read() * 10",
)
show_results('Adler32, separate', adler32.timeit(iterations), iterations)
adler32_running = timeit.Timer(
stmt="cksum = zlib.adler32(data, cksum)",
setup="import zlib; data=open('lorem.txt','r').read() * 10; cksum = zlib.adler32(data)",
)
show_results('Adler32, running', adler32_running.timeit(iterations), iterations)
crc32 = timeit.Timer(
stmt="zlib.crc32(data)",
setup="import zlib; data=open('lorem.txt','r').read() * 10",
)
show_results('CRC-32, separate', crc32.timeit(iterations), iterations)
crc32_running = timeit.Timer(
stmt="cksum = zlib.crc32(data, cksum)",
setup="import zlib; data=open('lorem.txt','r').read() * 10; cksum = zlib.crc32(data)",
)
show_results('CRC-32, running', crc32_running.timeit(iterations), iterations)
$ python zlib_checksum_tests.py
Adler32, separate: 0.99 usec/pass
Adler32, running: 0.98 usec/pass
CRC-32, separate: 9.72 usec/pass
CRC-32, running: 9.55 usec/pass
See also
- zlib
- 本モジュールの標準ライブラリドキュメント
- gzip
- gzip モジュールは zlib ライブラリに対する高レベル(ファイルベース)インタフェースを提供
- http://www.zlib.net/
- zlib ライブラリのホームページ
- http://www.zlib.net/manual.html
- 完全な zlib ドキュメント
- bz2
- bz2 モジュールは bzip2 圧縮ライブラリに対するよく似たインタフェースを提供