如何有效地读取非常大的 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 压缩日志文件的最后一行?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Java 中加速读写 base64 编码的 gzip 大文件