7

如果我有几个带有压缩 zlib 数据的二进制字符串,有没有办法在不解压缩所有内容的情况下有效地将它们组合成一个压缩字符串?

我现在必须做的例子:

c1 = zlib.compress("The quick brown fox jumped over the lazy dog. ")
c2 = zlib.compress("We ride at dawn! ")
c = zlib.compress(zlib.decompress(c1)+zlib.decompress(c2)) # Warning: Inefficient!

d1 = zlib.decompress(c1)
d2 = zlib.decompress(c2)
d = zlib.decompress(c)

assert d1+d2 == d # This will pass!

我想要的示例:

c1 = zlib.compress("The quick brown fox jumped over the lazy dog. ")
c2 = zlib.compress("We ride at dawn! ")
c = magic_zlib_add(c1+c2) # Magical method of combining compressed streams

d1 = zlib.decompress(c1)
d2 = zlib.decompress(c2)
d = zlib.decompress(c)

assert d1+d2 == d # This should pass!

我对 zlib 和 DEFLATE 算法了解不多,所以从理论上讲这可能是完全不可能的。另外,我必须使用 use zlib;所以我无法包装 zlib 并提出自己的协议来透明地处理级联流。

注意:如果解决方案在 Python 中不是微不足道的,我并不介意。我愿意编写一些 C 代码并在 Python 中使用 ctypes。

4

3 回答 3

7

由于您不介意涉足 C,您可以从查看gzjoin的代码开始。

请注意,gzjoin代码必须解压缩才能找到合并时必须更改的部分,但不必重新压缩。这还不错,因为解压缩通常比压缩快。

于 2013-02-07T06:13:31.887 回答
5

除了需要解压缩第一个 deflate 流的 gzjoin 之外,您还可以查看gzlog.hgzlog.c,它们可以有效地将短字符串附加到 gzip 文件中,而无需每次都解压缩 deflate 流。(它可以很容易地修改为对 zlib 包装的 deflate 数据而不是 gzip 包装的 deflate 数据进行操作。)如果您可以控制第一个 deflate 流的创建,则可以使用这种方法。如果您没有创建第一个 deflate 流,那么您将不得不使用需要解压缩的 gzjoin 方法。

这些方法都不需要重新压缩。

于 2013-02-07T16:34:19.003 回答
1

我只是将@zorlak 的评论变成了答案并添加了一些代码,以便我以后可以找到它。

如果您可以控制流的初始压缩,则可以将未压缩数据的长度、其 Adler-32 校验和以及压缩数据存储在某处。稍后,您可以按任意顺序连接各个流。

请注意,我不确定各个流是否可以具有不同的压缩级别、压缩策略或窗口大小,因为该concatenate函数会剥离除第一个流之外的所有 zlib 标头...

from typing import Tuple
import zlib


def prepare(data: bytes) -> Tuple[int, bytes, int]:
    deflate = zlib.compressobj()
    result = deflate.compress(data)
    result += deflate.flush(zlib.Z_SYNC_FLUSH)
    return len(data), result, zlib.adler32(data)


def concatenate(*chunks: Tuple[int, bytes, int]) -> bytes:
    if not chunks:
        return b''
    _, result, final_checksum = chunks[0]
    for length, chunk, checksum in chunks[1:]:
        result += chunk[2:]  # strip the zlib header
        final_checksum = adler32_combine(final_checksum, checksum, length)
    result += b'\x03\x00'  # insert a final empty block
    result += final_checksum.to_bytes(4, byteorder='big')
    return result


def adler32_combine(adler1: int, adler2: int, length2: int) -> int:
    # Python implementation of adler32_combine
    # The orignal C implementation is Copyright (C) 1995-2011, 2016 Mark Adler
    # see https://github.com/madler/zlib/blob/master/adler32.c#L143
    BASE = 65521
    WORD = 0xffff
    DWORD = 0xffffffff
    if adler1 < 0 or adler1 > DWORD:
        raise ValueError('adler1 must be between 0 and 2^32')
    if adler2 < 0 or adler2 > DWORD:
        raise ValueError('adler2 must be between 0 and 2^32')
    if length2 < 0:
        raise ValueError('length2 must not be negative')

    remainder = length2 % BASE
    sum1 = adler1 & WORD
    sum2 = (remainder * sum1) % BASE
    sum1 += (adler2 & WORD) + BASE - 1
    sum2 += ((adler1 >> 16) & WORD) + ((adler2 >> 16) & WORD) + BASE - remainder
    if sum1 >= BASE:
        sum1 -= BASE
    if sum1 >= BASE:
        sum1 -= BASE
    if sum2 >= (BASE << 1):
        sum2 -= (BASE << 1)
    if sum2 >= BASE:
        sum2 -= BASE

    return (sum1 | (sum2 << 16))

一个简单的例子:

hello = prepare(b'Hello World! ')
test = prepare(b'This is a test. ')
fox = prepare(b'The quick brown fox jumped over the lazy dog. ')
dawn = prepare(b'We ride at dawn! ')

# these all print what you would expect
print(zlib.decompress(concatenate(hello, test, fox, dawn)))
print(zlib.decompress(concatenate(dawn, fox, test, hello)))
print(zlib.decompress(concatenate(fox, hello, dawn, test)))
print(zlib.decompress(concatenate(test, dawn, hello, fox)))
于 2020-02-13T13:13:19.147 回答