如何快速获取gzip解压后的文件大小(无需定位)

4
一些StackOverflow答案表明,您可以使用decompressedSize = gzipFile.seek(0, io.SEEK_END)获取准确的gzip解压后文件大小。一些人还建议对于小于4 GiB的文件进行.seek(-4, 1)操作。然而,因为它在文件中查找直至结尾,对于较大的文件来说非常耗时(对于约1 GiB的解压数据,花费了几秒钟来寻找结尾)。
我接着尝试使用gunzip -l somefile.gz(同一个文件),它能够瞬间输出当前文件大小以及解压后的文件大小。
如果不是更快,我应该如何像gunzip一样快速获得解压后的gzip文件大小?
(附:我尝试获取解压缩gzip大小是为了CLI进度条在解压缩时使用)

1
你可以随时使用 subprocess 直接运行 gunzip - furas
2个回答

7
gzip -l 实际上是在寻找并读取文件的最后四个字节。你的评论“因为它在文件中寻找到最后,对于更大的文件来说非常耗时”表明你不理解什么是寻址。寻址不是读取整个文件直到结束。寻址是将文件的读指针移动到所需位置,并从那里读取。这需要 O(1) 的时间,而不是 O(n) 的时间(其中 n 是文件大小)。@crissal 的回答展示了如何正确使用。
这最后四个字节是最后一个 gzip 成员的未压缩长度模除2^32,假设 gzip 文件结尾没有垃圾数据。
你会注意到该句话中有三个警告。首先,正如你已经注意到的,未压缩的大小需要小于 2^32 字节,才能使该数字有意义。但是,你无法凭借查看压缩文件来确定是否满足此条件。gzip 可以压缩超过1024倍,因此gzip文件的长度可能仅为2^22字节(4MB),但解压后可以达到4GB以上。
第二个警告是gzip文件必须只有一个成员。gzip格式允许连接gzip成员,其中最后四个字节表示仅该最后成员的长度。没有可靠的方法可以找到其他成员,除非对整个gzip文件进行解码。
第三个警告是gzip文件结尾不能有任何垃圾数据。通常我在实际使用中没有看到这种情况,但是在gzip文件结尾可能存在填充的情况,这将再次使找到长度变得困难。
总之:如果你重要的是可靠地确定压缩大小,则只能在控制生成gzip文件并且可以确保内容小于4GB、只有一个成员并且结尾没有垃圾数据时才能使用最后四个字节。
对于你的应用程序,你不需要知道未压缩数据的长度。相反,你应该基于已处理的压缩数据的比例来显示进度条。你可以从文件系统获取文件的压缩大小,并知道到目前为止已经消耗了多少压缩数据。如果数据大致均匀,那么压缩比将在解压缩过程中大致保持不变。对于恒定的压缩比,压缩数据的进度条将显示与未压缩数据的进度条完全相同的内容。

6
未压缩的输入大小存储在最后4个字节[1]中,因此从-4开始的建议是正确的。
然而,问题在于您的光标必须在第二个参数之前移动4个位置,因此相对于文件末尾而不是当前位置移动4个位置。 因此,1(SEEK_CUR)应替换为2(SEEK_END)
一旦您设置了正确的位置,就可以read()只读取最后4个字节,然后将它们转换为int[2];字节顺序是小端。
with open("yourfile", "rb") as f:
  # place the cursor in the right position
  f.seek(-4, 2)

  # get the size of uncompressed input from last 4 bytes
  size = int.from_bytes( f.read(), "little" )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接