获得与 gunzip 一样快的 gzip 解压缩文件大小(无搜索)
Get gzip decompressed file size as fast as gunzip (no seek)
如一些 Whosebug 答案所示,您可以使用 decompressedSize = gzipFile.seek(0, io.SEEK_END)
获得精确的 gzip 解压缩文件大小。有些人还建议对小于 4 GiB 的文件执行 .seek(-4, 1)
。但是,因为它是从文件中查找到最后,所以对于更大的文件来说非常耗时(对于大约 1 GiB 的解压缩数据,查找到结尾需要几秒钟)。
然后我尝试使用gunzip -l somefile.gz
(同一个文件),它设法立即输出当前文件大小以及解压缩时的文件大小。
如果不是更快的话,我如何才能像 gunzip
一样快地获得解压缩的 gzip 文件大小?
(P.S。我尝试获取解压后的 gzip 大小的原因是为了解压时的 CLI 进度条)
未压缩的输入大小存储在最后 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" )
gzip -l
实际上是在寻找并读取文件的最后四个字节。您的评论“因为它一直在寻找文件,所以对于更大的文件来说非常耗时”表明您不了解什么是寻找。搜索不是读取整个文件,直到你到达结尾。查找是将文件的读指针移动到所需的点,并从那里读取。它需要 O(1) 时间,而不是 O(n) 时间(其中 n 是文件的大小)。 @crissal 的回答显示了如何正确执行此操作。
最后四个字节是最后一个 gzip 成员的未压缩长度,模 232,假设 gzip 文件末尾没有垃圾。
你会注意到这句话中的三个警告。首先,正如您已经注意到的,uncompressed 大小需要小于 232 字节才能使该数字有意义。但是,您不一定能通过查看压缩文件来判断这是真是假。 gzip 可以压缩到 1024 倍以上,因此 gzip 文件的长度可能只有 222 字节,4 MB,但解压缩后会超过 4 GB。
第二个警告是 gzip 文件必须只有一个成员。 gzip 格式允许串联的 gzip 成员,最后四个字节只代表最后一个成员的长度。除了解码整个 gzip 文件外,没有找到其他成员的可靠方法。
第三个注意事项是 gzip 文件末尾没有任何垃圾。总的来说,我没有在野外看到过,但是 gzip 文件的末尾可能会有填充,这会再次混淆查找长度。
底线:如果可靠地确定压缩大小对您很重要,那么您可以使用最后四个字节 仅 如果您可以控制压缩文件的生成gzip文件,保证内容<4GB,只有一个成员,最后没有垃圾。
对于您的应用程序,您不需要知道未压缩数据的长度。您应该将进度条建立在到目前为止已处理的 compressed 数据的分数上。您知道文件系统中文件的压缩大小,并且您知道到目前为止您消耗了多少压缩数据。如果数据近似均匀,则在整个解压缩过程中压缩率将近似恒定。对于恒定的压缩率,压缩数据进度条将显示 与未压缩数据进度条完全相同的内容。
如一些 Whosebug 答案所示,您可以使用 decompressedSize = gzipFile.seek(0, io.SEEK_END)
获得精确的 gzip 解压缩文件大小。有些人还建议对小于 4 GiB 的文件执行 .seek(-4, 1)
。但是,因为它是从文件中查找到最后,所以对于更大的文件来说非常耗时(对于大约 1 GiB 的解压缩数据,查找到结尾需要几秒钟)。
然后我尝试使用gunzip -l somefile.gz
(同一个文件),它设法立即输出当前文件大小以及解压缩时的文件大小。
如果不是更快的话,我如何才能像 gunzip
一样快地获得解压缩的 gzip 文件大小?
(P.S。我尝试获取解压后的 gzip 大小的原因是为了解压时的 CLI 进度条)
未压缩的输入大小存储在最后 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" )
gzip -l
实际上是在寻找并读取文件的最后四个字节。您的评论“因为它一直在寻找文件,所以对于更大的文件来说非常耗时”表明您不了解什么是寻找。搜索不是读取整个文件,直到你到达结尾。查找是将文件的读指针移动到所需的点,并从那里读取。它需要 O(1) 时间,而不是 O(n) 时间(其中 n 是文件的大小)。 @crissal 的回答显示了如何正确执行此操作。
最后四个字节是最后一个 gzip 成员的未压缩长度,模 232,假设 gzip 文件末尾没有垃圾。
你会注意到这句话中的三个警告。首先,正如您已经注意到的,uncompressed 大小需要小于 232 字节才能使该数字有意义。但是,您不一定能通过查看压缩文件来判断这是真是假。 gzip 可以压缩到 1024 倍以上,因此 gzip 文件的长度可能只有 222 字节,4 MB,但解压缩后会超过 4 GB。
第二个警告是 gzip 文件必须只有一个成员。 gzip 格式允许串联的 gzip 成员,最后四个字节只代表最后一个成员的长度。除了解码整个 gzip 文件外,没有找到其他成员的可靠方法。
第三个注意事项是 gzip 文件末尾没有任何垃圾。总的来说,我没有在野外看到过,但是 gzip 文件的末尾可能会有填充,这会再次混淆查找长度。
底线:如果可靠地确定压缩大小对您很重要,那么您可以使用最后四个字节 仅 如果您可以控制压缩文件的生成gzip文件,保证内容<4GB,只有一个成员,最后没有垃圾。
对于您的应用程序,您不需要知道未压缩数据的长度。您应该将进度条建立在到目前为止已处理的 compressed 数据的分数上。您知道文件系统中文件的压缩大小,并且您知道到目前为止您消耗了多少压缩数据。如果数据近似均匀,则在整个解压缩过程中压缩率将近似恒定。对于恒定的压缩率,压缩数据进度条将显示 与未压缩数据进度条完全相同的内容。