使用Python的zlib压缩数据的最大大小

vktxenjb  于 2023-06-04  发布在  Python
关注(0)|答案(2)|浏览(183)

我正在编写一个Python库,它以流的方式生成ZIP文件。如果zip的成员的未压缩或压缩数据为4GiB或更大,则必须使用原始ZIP格式的特定扩展-zip 64。总是使用它的问题是它的支持较少。所以,如果需要的话,我只想使用zip 64。但是文件是否是zip 64必须在压缩数据之前在zip * 中指定,因此如果是流式传输,则在压缩数据的大小已知之前。
然而,在某些情况下,未压缩数据的大小 * 是 * 已知的。因此,我想根据这个未压缩的大小预测zlib可以输出的 * 最大 * 大小,如果是4GiB或更大,请使用zip 64模式。
换句话说,如果下面的chunks的总长度是已知的,那么get_compressed可以产生的最大字节总长度是多少?(我假设这个最大大小取决于level、memLevel和wbits)

import zlib

chunks = (
    b'any',
    b'iterable',
    b'of',
    b'bytes',
    b'-' * 1000000,
)

def get_compressed(level=9, memLevel=9, wbits=-zlib.MAX_WBITS):
    compress_obj = zlib.compressobj(level=level, memLevel=memLevel, wbits=wbits)
    for chunk in chunks:
        if compressed := compress_obj.compress(chunk):
            yield compressed

    if compressed := compress_obj.flush():
        yield compressed

print('length', len(b''.join(get_compressed())))

这是复杂的事实,Python zlib module's behaviour is not consistent between Python versions
我认为Java在不知道未压缩数据大小的情况下尝试了一种“自动zip 64模式”,但是libarchive has problems with it

8i9zcol2

8i9zcol21#

你可以通过压缩一些随机数据来估计它。1000个块的压缩大小,每个块1000字节,具有不同的参数:

level=0:  1000155 (+0.015%)
level=1:  1000155 (+0.015%)
level=2:  1000155 (+0.015%)
level=3:  1000155 (+0.015%)
level=4:  1000155 (+0.015%)
level=5:  1000155 (+0.015%)
level=6:  1000155 (+0.015%)
level=7:  1000155 (+0.015%)
level=8:  1000155 (+0.015%)
level=9:  1000155 (+0.015%)
memLevel=1:  1039350 (+3.935%)
memLevel=2:  1019600 (+1.960%)
memLevel=3:  1009780 (+0.978%)
memLevel=4:  1004885 (+0.488%)
memLevel=5:  1002445 (+0.245%)
memLevel=6:  1001225 (+0.122%)
memLevel=7:  1000615 (+0.061%)
memLevel=8:  1000310 (+0.031%)
memLevel=9:  1000155 (+0.015%)

对于每个2000字节的2000块:

level=0:  4000590 (+0.015%)
level=1:  4000610 (+0.015%)
level=2:  4000610 (+0.015%)
level=3:  4000610 (+0.015%)
level=4:  4000615 (+0.015%)
level=5:  4000615 (+0.015%)
level=6:  4000615 (+0.015%)
level=7:  4000615 (+0.015%)
level=8:  4000615 (+0.015%)
level=9:  4000615 (+0.015%)
memLevel=1:  4157400 (+3.935%)
memLevel=2:  4078390 (+1.960%)
memLevel=3:  4039120 (+0.978%)
memLevel=4:  4019540 (+0.488%)
memLevel=5:  4009770 (+0.244%)
memLevel=6:  4004885 (+0.122%)
memLevel=7:  4002445 (+0.061%)
memLevel=8:  4001225 (+0.031%)
memLevel=9:  4000615 (+0.015%)

所以看起来如果你只改变level,它的开销大约是0.015%。

import zlib
import os

chunks = [
  os.urandom(1000)
  for _ in range(1000)
]

def get_compressed(level=9, memLevel=9, wbits=-zlib.MAX_WBITS):
    compress_obj = zlib.compressobj(level=level, memLevel=memLevel, wbits=wbits)
    for chunk in chunks:
        if compressed := compress_obj.compress(chunk):
            yield compressed

    if compressed := compress_obj.flush():
        yield compressed

insize = sum(map(len, chunks))
for level in range(10):
    compressed = get_compressed(level=level)
    outsize = len(b''.join(compressed))
    print(f'{level=}: ', outsize, f'({(outsize-insize)/insize:+.3%})')

for memLevel in range(1, 10):
    compressed = get_compressed(memLevel=memLevel)
    outsize = len(b''.join(compressed))
    print(f'{memLevel=}: ', outsize, f'({(outsize-insize)/insize:+.3%})')

Attempt This Online!

qvk1mo1f

qvk1mo1f2#

当然,你可以找到这个。但是,您依赖的是zlib特定版本的详细的、未记录的行为。zlib中的Deflate可以被修改或重写,然后你的代码就被破坏了。
即使您有不可压缩数据的确切界限,您仍然可能最终得到标记为需要Zip64的条目,而这些条目不需要。例如,如果数据是可压缩的,但边界将其推过。
此外,如果流拉链是真正的流,则它应该能够接受流输入,在这种情况下,它首先不知道未压缩的大小是多少。所以这没用。
处理流zipper的正确方法是将本地头标记为不需要Zip64。一旦发现它确实需要Zip64,就使用适当的数据描述符,并将中央目录中的条目标记为需要Zip64。如果解压缩器使用的是中央目录,就像大多数解压缩器一样,那么它就有正确的信息。如果unzipper是流式的,那么它必须尝试所有可能的数据描述符 * 无论如何 *,所以本地头声明什么并不重要。

相关问题