Python 读取gzip文件的头/尾,而不将其读入内存

Python 读取gzip文件的头/尾,而不将其读入内存,python,gzip,syslog,Python,Gzip,Syslog,可能重复: 我有一个7GB的gzip系统日志文件,可以提取超过25GB的数据。我只需要检索文件的第一行和最后一行,而不需要立即将整个文件读入内存 在Python2.7中,允许使用读取头部(通过与进行迭代意味着我不必读取整个文件): Python 2.6版本可避免出现诸如AttributeError:gzip文件实例没有属性“\uuuu exit\uuuuu”(因为不支持上的迭代) 问题是我没有办法找回尾巴。。。不支持负值,并且我无法找到在不遍历25GB文件的情况下检索最后一行的方法(这需要花费

可能重复:

我有一个7GB的gzip系统日志文件,可以提取超过25GB的数据。我只需要检索文件的第一行和最后一行,而不需要立即将整个文件读入内存

在Python2.7中,允许使用读取头部(通过
进行迭代意味着我不必读取整个文件):

Python 2.6版本可避免出现诸如
AttributeError:gzip文件实例没有属性“\uuuu exit\uuuuu”
(因为不支持
上的
迭代)

问题是我没有办法找回尾巴。。。不支持负值,并且我无法找到在不遍历25GB文件的情况下检索最后一行的方法(这需要花费很长的时间)


在不将整个文件读入内存或遍历所有行的情况下,读取gzip文本文件尾部最有效的方法是什么?如果无法做到这一点,请解释原因。

没有办法做到这一点。DEFLATE是一种流压缩算法,这意味着如果不解压缩文件之前的所有内容,就无法解压缩文件的任意部分。

gzip文件是一个流,因此您必须通读它才能到达最后一行

from gzip import GzipFile
from collections import deque
dq = deque(maxlen=1)
with GzipFile('firewall.4.gz') as file:
    head = next(file)
    dq.extend(file)
tail = dq[0]

gzip使用的deflate格式通过在数据前32K的某个位置找到匹配字符串,并使用带有偏移量和长度的字符串引用进行部分压缩。因此,在任何一点上,从该点解压的能力取决于最后32K,它本身取决于它前面的32K,依此类推回到开始。因此,要在流中的任意点x解压数据,需要先解压从0到x-1的所有内容

有几种方法可以缓解这种情况。首先,如果您想频繁地随机访问gzip文件,那么您愿意完成一次扫描整个gzip文件并建立索引的工作。索引将在其中保存一些入口点中的每个入口点的前32K,这些入口点的密度决定了随机访问的速度。在中,您可以在中看到这方面的示例

如果您控制gzip文件的生成,则可以使用
Z_FULL_FLUSH
FLUSH选项定期擦除这些点上最后32K的历史记录,以允许随机访问条目。然后,将这些点的位置保存为索引,这将不需要每个入口点的32K历史块。如果这些点不够频繁,对压缩的影响就会非常小

只要能够编写gzip输出,您就可以通过简单地编写连接的gzip流来完成类似于
Z_FULL_FLUSH
的操作,而只需要稍微增加一些开销
gunzip
将接受并解码与
cat
命令组合在一起的gzip流,并将写出一个未压缩数据流。您可以用这种方式建立一个大的gzip日志,记住每个gzip片段开头的偏移量

如果您只对尾部感兴趣,那么您可以按照您在其中一条注释中的建议进行操作,即只需在大型gzip文件尾部的其他位置维护一个缓存


我不知道你是否正在制作日志文件。如果是,您可能希望查看zlib源代码发行版中再次找到的。

公平点,因此,解决此问题的最佳解决方案可能是在旋转/压缩之前抓住文件的尾部并将其作为我的
logrotate
脚本的一部分存档。您也可以使用bzip2,因为它是一种块压缩算法。取文件的最后5MB左右,在其上运行
bzip2recover
,并对其留下的内容进行排序。@mike pennington:您似乎认为这回答了您的问题,但您的书面问题不是如何在不扫描整个输入的情况下进行排序,而是如何在不立即将整个文件读入内存的情况下获取最后一行。这是你的问题吗?如果是这样的话,那么GzipFile是可能的,正如gnibbler的回答所示。仅供参考,我在一个较小的日志文件(10.2GB)上尝试了
collections.deque()。。。遍历整个文件几乎花费了90秒。我一次需要在100多个文件上运行此程序,因此
deque
对于此应用程序可能不现实fwiw:我开发了
gztool
(),它实现了@mark adler指出的思想,因此允许您在创建索引后随机访问gzip文件。使用
gztool
也可以通过任何操作(tail、从#字节提取、连续tailing等)创建索引,因此不会第一次增加提取时间,而且如果要多次访问同一文件,这非常方便。
>>> from itertools import islice
>>> from gzip import GzipFile
>>> class GzipFileHack(GzipFile):
...     def __enter__(self):
...         return self
...     def __exit__(self, type, value, tb):
...         self.close()
>>> with GzipFileHack('firewall.4.gz') as file:
...     head = list(islice(file, 1))
from gzip import GzipFile
from collections import deque
dq = deque(maxlen=1)
with GzipFile('firewall.4.gz') as file:
    head = next(file)
    dq.extend(file)
tail = dq[0]