Python 将多个zlib压缩数据流高效地连接到单个流中

Python 将多个zlib压缩数据流高效地连接到单个流中,python,zlib,Python,Zlib,如果我有几个带有压缩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:

如果我有几个带有压缩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算法了解不多,所以从理论角度来看,这可能是完全不可能的。而且,我必须使用zlib;因此,我不能包装zlib,并提出我自己的协议,透明地处理连接流


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

既然您不介意尝试使用C,那么您可以从查看C代码开始


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

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

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

我只是把答案转换成一个答案,然后添加一些代码,以便以后可以找到它

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

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

从键入导入元组
进口zlib
def prepare(数据:字节)->元组[int,bytes,int]:
deflate=zlib.compressobj()
结果=放气。压缩(数据)
结果+=放气.冲洗(zlib.Z_同步\u冲洗)
返回len(数据),结果,zlib.adler32(数据)
def concatenate(*块:元组[int,bytes,int])->字节:
如果不是块:
返回b''
_,结果,最终校验和=块[0]
对于块中的长度、块、校验和[1:]:
结果+=块[2:]#去除zlib头
最终校验和=adler32联合收割机(最终校验和,校验和,长度)
结果+=b'\x03\x00'#插入最后一个空块
结果+=最终校验和到字节(4,byteorder='big')
返回结果
def adler32_联合收割机(adler1:int、adler2:int、长度2:int)->int:
#adler32_combine的Python实现
#原始C实现版权所有(C)1995-2011、2016 Mark Adler
#看https://github.com/madler/zlib/blob/master/adler32.c#L143
基数=65521
WORD=0xffff
DWORD=0xFFFFFF
如果adler1<0或adler1>DWORD:
raise VALUERROR('adler1必须介于0和2^32'之间)
如果adler2<0或adler2>DWORD:
raise VALUERROR('adler2必须介于0和2^32'之间)
如果长度2<0:
raise VALUE ERROR('长度2不得为负')
剩余=长度2%基准
sum1=adler1&WORD
sum2=(余数*sum1)%BASE
sum1+=(adler2和WORD)+BASE-1
sum2+=((adler1>>16)和WORD)+((adler2>>16)和WORD)+基-余数
如果sum1>=基准:
sum1-=基础
如果sum1>=基准:
sum1-=基础

如果sum2>=(基本上这并不像我希望的那么简单,但它肯定比我以前做的更有效。谢谢。具体地回答这个问题,你不能像gzjoin那样在不解压缩第一个放气流的情况下合并两个放气流。尽管如果你能控制创建第一个放气流,它可以专门准备应用在不解压缩的情况下结束。您可以使用
Z_SYNC\u FLUSH
(或
Z_FULL\u FLUSH
——在这种情况下无所谓)完成压缩,确保在此之后获得所有压缩数据(
strm.avail\u out!=0
),然后用一个
Z_FINISH
。然后你可以去掉流的最后两个字节,并直接将另一个deflate流连接到该流。为了找到内心的平静,接受你自己的现状,接受别人的现状。@MarkAdler谢谢,你为我们指明了正确的方向。我正在与meawoppl合作开发一个并行PNG compressor。我们无法使用您建议的方法连接两个流,但我们能够以这种方式工作:对于第一个流,使用Z_SYNC_FLUSH调用deflate。对于每个后续流,使用Z_SYNC_FLUSH调用deflate,去掉前两个字节,然后连接,还收集adler32值和未压缩的长度。然后对于最后一个流,使用Z_FINISH放气,去掉4字节校验和,并用adler32_combine()替换它在所有的校验和中。很高兴知道zlib的作者正在回答这个问题:D我将研究这些。感谢您对自由软件世界的贡献!您说过
gzjoin
技术只需要解压缩第一个流。但是,代码似乎对所有流都一视同仁(并调用
inflate()
(在所有设备上).我误读了吗?还有,你说如果第一个流是专门构造的,就不需要解压缩。是否可以专门构造一个原始输入数据为零字节的流,然后每次都将其用作第一个流?你可以通过只解码第一个流来组合两个deflate流。但是gzip流没有总是在结尾处结束