如何有效地读取非常大的 gzip 压缩日志文件的最后一行?

Posted

技术标签:

【中文标题】如何有效地读取非常大的 gzip 压缩日志文件的最后一行?【英文标题】:How to efficiently read the last line of very big gzipped log file? 【发布时间】:2021-11-06 00:22:20 【问题描述】:

我想从一个大的 gzip 日志文件中获取最后一行,不必遍历所有其他行,因为它是一个大文件。

我已阅读 Print Last Line of File Read In with Python 尤其是 this answer 的大文件,但它不适用于 gzip 压缩文件。确实,我试过了:

import gzip

with gzip.open(f, 'rb') as g:
    g.seek(-2, os.SEEK_END) 
    while g.read(1) != b'\n':  # Keep reading backward until you find the next break-line
        g.seek(-2, os.SEEK_CUR) 
    print(g.readline().decode())

但在我非常标准的笔记本电脑上,10 MB 压缩/130 MB 解压缩文件已经花费了 80 多秒!

问题:如何使用 Python 高效地查找 gzip 文件的最后一行?


旁注:如果不压缩,此方法非常快:130 MB 文件需要 1 毫秒:

import os, time
t0 = time.time()
with open('test', 'rb') as g:
    g.seek(-2, os.SEEK_END) 
    while g.read(1) != b'\n': 
        g.seek(-2, os.SEEK_CUR) 
    print(g.readline().decode())
print(time.time() - t0)    

【问题讨论】:

【参考方案1】:

如果您无法控制 gzip 文件的生成,那么如果不解码所有行,就无法读取未压缩数据的最后一行。它花费的时间将是 O(n),其中 n 是文件的大小。没有办法使它成为 O(1)。

如果您确实可以控制压缩端,那么您可以创建一个便于随机访问的 gzip 文件,还可以跟踪随机访问入口点以启用跳转到文件末尾。

【讨论】:

gzip 模块的 API 可能不支持这一点,但理论上gzip 算法会支持从一个一个向后读取字节结束?如果是这样,读取last行的时间应该等于读取第一行的时间,对吗? 感谢您的回答@MarkAdler。我不知道我是在与 GNU gzip 和 zlib 的创建者交谈,尊敬的!【参考方案2】:

缓慢可能是由于循环中多次调用seek

所以这个只有一个seek 的解决方案有效:

with gzip.open(f, 'rb') as g:
    g.seek(-1000, os.SEEK_END)  # go 1000 bytes before end
    l = g.readlines()[-1].decode() # the last line

注意:

g.readlines() 在这里很快,因为它只将最后 1000 个字节分成几行 根据文件中可能出现的最长行更改 1000

仍在寻找更好的解决方案。这是链接的,但没有提供获取最后一行的真正解决方案:Lazy Method for Reading Big File in Python?

【讨论】:

问题在于,如果不知道压缩数据的结尾是什么,就无法解释它的结尾。这就是您对压缩所做的权衡:牺牲访问时间以换取节省空间。 @BoarGules 从开始读取一行非常快(使用for line in g: break):它读取字节直到达到\n(或多或少)。所以从技术上讲,应该有一种方法可以向后执行相同的操作:从末尾读取,字节接字节,并在\n 存在时停止。从技术上讲,从结尾阅读应该和从头开始阅读一样快。 @DarkKnight 如果它没有压缩,不,我不这么认为:我们可以将光标移动到 EOF,并以相反的顺序在循环中读取一个字节(文件从当前位置),并在遇到\n 时停止。这应该与读取第一行的速度相同。 @DarkKnight 我刚刚在 1 分钟前做过,我确认,如果不是 gzip 压缩,这种方法非常快:130 MB 文件需要 1 毫秒。我刚刚更新了问题,为非压缩情况添加了此代码。 @Basj 这种情况对于原始数据来说是微不足道的,而对于压缩数据来说则不是这样,因为 DEFLATE 意味着块的所有数据都取决于块声明及其前面的数据(在块中)。 DEFLATE 流是 bit 流,其中块具有非常简单的 3 个 bits 标头,因此 deflate 流不是 self-synchronising:来自流中的随机点无法发现当前块从哪里开始,或者下一个块从哪里开始。

以上是关于如何有效地读取非常大的 gzip 压缩日志文件的最后一行?的主要内容,如果未能解决你的问题,请参考以下文章

将压缩的csv拆分为块的最有效方法

如何在 Java 中加速读写 base64 编码的 gzip 大文件

有效地读取 R 中的一个非常大的文本文件 [重复]

如何使用带有 gzip 压缩选项的 pandas read_csv 读取 tar.gz 文件?

PHP打开gzip压缩的XML

如何实现,读取远程文件,用GZIP压缩后保存成文件