如何无需seek操作,达到与gunzip相当速度获取gzip解压后文件大小
核心原理
gzip格式规范中,每个gzip数据块的末尾固定存储4个字节的小端序32位无符号整数,代表该块未压缩数据的大小(数值为原始大小对2^32取模的结果)。gunzip -l之所以能瞬间返回结果,是因为它不会解压完整文件,只需要读取文件末尾的4字节,或者遍历所有gzip块的尾数字段累加,不需要处理压缩数据本身。
你之前用到的gzipFile.seek(0, io.SEEK_END)速度慢,是因为Python内置gzip模块的seek是基于未压缩字节偏移的逻辑寻址,需要逐段解压前面的内容计算偏移位置,和真实的物理文件跳转完全是两回事,大文件场景下自然耗时极高。另外之前流传的.seek(-4, 1)属于参数错误,1代表以当前指针位置为基准寻址,正确的操作是以文件末尾为基准,对应参数是os.SEEK_END(常量值为2)。
实现方案
场景1:确认未压缩文件大小不超过4 GiB
直接读取压缩包物理文件的最后4个字节解析即可,耗时稳定在毫秒级,和gunzip -l速度一致,代码示例(Python):
import os import struct def get_gzip_uncompressed_size(gzip_path: str) -> int: with open(gzip_path, 'rb') as f: # 直接跳转物理文件末尾前4字节 f.seek(-4, os.SEEK_END) # 小端序解析32位无符号整数 return struct.unpack('<I', f.read(4))[0]
注意:该方法返回的数值是原始大小对
2^32取模的结果,如果原始文件大于4 GiB,直接读取单尾部4字节会得到错误值。
场景2:适配大于4 GiB或多成员gzip文件
如果需要兼容所有gzip格式,只需要遍历gzip的每个数据块,跳过压缩数据段,直接读取每个块末尾的4字节大小累加即可。整个过程不需要解压数据,仅需读取压缩包的元数据部分,速度和直接读完整压缩包的磁盘IO速度一致,远快于全量解压。
适配CLI进度条场景的优化建议
如果你的应用场景无法提前确认原始文件大小上限,可以先用尾部4字节的结果作为进度条初始总大小,解压过程中如果实际已输出的字节数超过初始总大小,就动态把总大小调整为当前总大小 + 4 GiB,多次调整直到解压完成即可,用户感知非常弱,同时可以兼顾绝大多数场景下的进度展示准确性。
内容的提问来源于stack exchange,提问作者Programer Beginner

