在Python中获取一个.gz文件的未压缩大小

17

使用gzip时,tell()返回未压缩文件中的偏移量。
为了显示进度条,我想知道文件的原始(未压缩)大小。
有没有一种简单的方法来找出它?

11个回答

24

未压缩大小存储在gzip文件的最后4个字节中。我们可以读取二进制数据并将其转换为int。(仅适用于小于4GB的文件)

import struct

def getuncompressedsize(filename):
    with open(filename, 'rb') as f:
        f.seek(-4, 2)
        return struct.unpack('I', f.read(4))[0]

打开文件时使用 "rb" 模式来避免 error: unpack requires a string argument of length 4 错误。 - slv
1
这正是老 Jorge Israel Peña 答案中所展示的内容,因此虽然您的答案提供了一个方便的函数,但并没有为主题增添太多内容。此外,正如旧答案中的评论所说,仅依赖于最后4个字节实际上并不是100%可靠的,因为GZ允许您在文件末尾附加新块。 - quetzalcoatl

18

gzip格式指定了一个叫做ISIZE的字段:

该字段包含原始(未压缩)输入数据模2^32的大小。

gzip.py中,我假设这是您用于gzip支持的内容,有一个名为_read_eof的方法,其定义如下:

def _read_eof(self):
    # We've read to the end of the file, so we have to rewind in order
    # to reread the 8 bytes containing the CRC and the file size.
    # We check the that the computed CRC and size of the
    # uncompressed data matches the stored values.  Note that the size
    # stored is the true file size mod 2**32.
    self.fileobj.seek(-8, 1)
    crc32 = read32(self.fileobj)
    isize = U32(read32(self.fileobj))   # may exceed 2GB
    if U32(crc32) != U32(self.crc):
        raise IOError, "CRC check failed"
    elif isize != LOWU32(self.size):
        raise IOError, "Incorrect length of data produced"

在这里,你可以看到正在读取 ISIZE 字段,但仅用于与 self.size 进行比较以进行错误检测。这意味着 GzipFile.size 存储的应该是实际未压缩大小。然而,我认为它并没有公开暴露,因此您可能需要进行一些修改才能暴露它。不太确定,请见谅。

我刚刚查阅了所有这些信息,我还没有尝试过,所以我可能是错的。希望对你有所帮助。如果我误解了你的问题,请见谅。


我想这已经足够好了。如果文件大于4G,可以很容易地向进度条添加一些启发式算法,将文件大小设置为4G + ISIZE,如果tell()指示我们离ISIZE太近。 - Paul Oyster
我需要做同样的事情,我正在尝试扩展GzipFile类以提供文件大小,但我不成功,你是如何让它工作的? - lanrat
更新:这个函数对我来说可用:http://code.activestate.com/lists/python-list/245777/ - lanrat
2
请注意,这并不是完全可靠的,因为附加到的gzip文件只会具有最后一个附加部分的大小...请参见:http://pastebin.com/82zyV3k9 - 这里的第二个“1000”实际上应该是2000,但它只是被附加的最后一个块的大小... - Matt Billenstein
如果未压缩的文件大小大于4GiB,则该值将不正确 - 此答案展示了一种估算它的方法,而无需读取整个文件。 - Álvaro Justen

14

尽管其他答案声称最后四个字节是获取gzip文件未压缩长度的可靠方法,但这并不准确。首先,gzip文件中可能有多个成员,所以这只会是最后一个成员的长度。其次,长度可能超过4 GB,在这种情况下,最后四个字节表示长度 modulo 232,而不是长度本身。

然而,对于您想要的内容,没有必要获得未压缩长度。相反,您可以基于已经获得的gzip文件的长度来计算进度条,此时进度条是基于已消耗的输入量与gzip文件长度之比而显示的。对于典型的同构数据,这种进度条将显示与基于未压缩数据的进度条完全相同的信息。


1
马克,最近我正在进行一些gzip文件的编程操作,并经常看到你的答案在堆栈底部只有一两个投票。我猜人们不认识你。感谢您的巨大贡献,尽管缺乏认可,仍然为压缩问题提供了您的答案。 - mdisibio
在将一个gzipped CSV文件导入到PostgreSQL时(rows pgimport),我使用已读取的未压缩数据量实现了一个进度条,同时还添加了总未压缩大小的估计值。如果程序发现估计值错误,它会将其更新为已读取的总量(此答案提供了超过4GiB的Python代码来估算大小)。 - Álvaro Justen
1
@ÁlvaroJusten 嗯,为什么?你必须知道压缩文件的大小才能尝试估计。在这种情况下,只需根据已消耗的压缩数据的分数来设置进度条即可解决问题。 - Mark Adler
1
@MarkAdler 我使用Python的os.stat()函数来获取压缩后的文件大小。我更喜欢在进度条上看到未压缩的文件大小,因为这是将要插入数据库的数量。 - Álvaro Justen

5
Unix的方式:通过subprocess.call/os.popen使用“gunzip -l file.gz”,捕获并解析其输出。

2
从严肃的角度讲:我正在寻找一个Python解决方案,因为该代码适用于所有平台。不要碰比我老的操作系统... - Paul Oyster
2
Windows 至少有 24 或 25 岁了。第一个版本大约在 1985 年左右发布。你今年多少岁? - jmucchiello
1
44.5(并在18岁时最后使用过Unix) - Paul Oyster
请注意,这基本上与@Jorge Israel Peña答案建议的做法相同,因此对于大于4GB的文件将无效。 - Chen Levy

4
.gz 文件的最后4个字节保存了文件的原始大小。

4
最后4个字节是“原始(未压缩)输入数据大小模2^32的余数”。(来源:http://www.gzip.org/zlib/rfc-gzip.html) - Gumbo

2

我不确定性能如何,但是可以通过以下方法实现,而无需了解 gzip 的魔术:

with gzip.open(filepath, 'rb') as file_obj:
    file_size = file_obj.seek(0, io.SEEK_END)

这也适用于其他(压缩的)流读取器,如bz2或普通的open

编辑: 如评论中建议的那样,第二行中的2被替换为io.SEEK_END,这显然更易读,可能更具未来性。

编辑: 仅适用于Python 3。


1
file_size = file_obj.seek(0, io.SEEK_END) - teichert
Python 3!不支持Python 2!“ValueError:不支持从末尾查找”。 但是:struct.unpack适用于2.7! - ewerybody
不幸的是,对于使用gzip库处理大型.GZ文件时,file_obj.seek(0, io.SEEK_END)(或者顺便提一下,os.SEEK_END)需要很长时间 - 是否需要解压缩才能到达流的末尾? - Maksym Ganenko

1
    f = gzip.open(filename)
    # kludge - report uncompressed file position so progess bars
    # don't go to 400%
    f.tell = f.fileobj.tell

0

查看 gzip 模块的源代码,我发现 GzipFile 的基础文件对象似乎是 fileobj。因此:

mygzipfile = gzip.GzipFile()
...
mygzipfile.fileobj.tell()

?

在执行此操作之前,进行一些健全性检查可能是有益的,例如使用hasattr检查属性是否存在。

虽然不完全是公共API,但是......


.tell() 的效果很好。我正在寻找的是原始文件大小。 - Paul Oyster

0

GzipFile.size 存储了未压缩的大小,但它只有在读取文件时才会递增,因此您应该优先使用 len(fd.read()) 而不是非公开的 GzipFile.size。


1
文件很大怎么办? - allyourcode
请注意,正如@allyourcode在这里建议的那样,len(df.read())会强制Python将整个文件保存在内存中。对于非常大的文件,这可能会导致进程崩溃。 - Chen Levy

0

这里是一个Python2版本的@noroksolution

import gzip, io

with oepn("yourfile.gz", "rb") as f:
    prev, cur = 0, f.seek(1000000, io.SEEK_CUR)
    while prev < cur:
        prev, cur = cur, f.seek(1000000, io.SEEK_CUR)

filesize = cur

请注意,就像 f.seek(0, io.SEEK_END) 一样,这对于大文件来说非常慢,但它将克服更快的解决方案所建议的此处4GB大小限制

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