逐行流式处理一个 .zst 压缩文件

4

我正在尝试筛选一个被压缩在.zst文件中的大型数据库。我知道我可以简单地解压它,然后处理生成的文件,但这会占用我的SSD很多空间并且需要2个小时以上,所以如果可能的话,我想避免这种情况。

通常当我处理大文件时,我会使用逐行流式传输的代码。

with open(filename) as f:
    for line in f.readlines():
        do_something(line)

我知道gzip有这个功能

with gzip.open(filename,'rt') as f:
    for line in f:
        do_something(line)

但是它似乎不能与.zsf一起使用,因此我想知道是否有任何库可以解压缩并以类似的方式流式传输已解压缩的数据。例如:

with zstlib.open(filename) as f:
    for line in f.zstreadlines():
        do_something(line)
1个回答

3
了解要使用哪个软件包以及相应的文档可能会有些困惑,因为实际上有几个Python绑定到Zstandard库。
下面,我指的是Gregory Szorc的库,我是通过conda的默认渠道安装的:
conda install zstd

# check:

conda list zstd
# # Name                    Version                   Build  Channel
# zstd                      1.5.5                hc292b87_0  

尽管文档上说要使用pip安装(但除非没有其他方法,我不会这样做,因为我希望我的conda环境保持可用),我只是推测这个版本是G. Szorc的版本,基于在__init__.py文件中的注释。
# Copyright (c) 2017-present, Gregory Szorc
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.

"""Python interface to the Zstandard (zstd) compression library."""

from __future__ import absolute_import, unicode_literals

# This module serves 2 roles:
#
# 1) Export the C or CFFI "backend" through a central module.
# 2) Implement additional functionality built on top of C or CFFI backend.

所以,我认为相应的文档在这里。
无论如何,在安装后进行快速测试:
import zstandard as zstd

with zstd.open('test.zstd', 'w') as f:
    for i in range(10_000):
        f.write(f'foo {i} bar\n')

with zstd.open('test.zstd', 'r') as f:
    for i, line in enumerate(f):
        if i % 1000 == 0:
            print(f'line {i:4d}: {line}', end='')

产生:

line    0: foo 0 bar
line 1000: foo 1000 bar
line 2000: foo 2000 bar
line 3000: foo 3000 bar
line 4000: foo 4000 bar
line 5000: foo 5000 bar
line 6000: foo 6000 bar
line 7000: foo 7000 bar
line 8000: foo 8000 bar
line 9000: foo 9000 bar

注意事项:
如果文件是以二进制(而不是文本)编写的,则使用mode='rb',与普通文件相同。底层文件始终以二进制模式编写,但如果我们在open中使用文本模式,那么根据open的文档,"(...)在文本模式下读取或写入时,返回一个io.TextIOWrapper。"
请注意,我使用的是f的迭代器,而不是readlines()。从内联文档字符串中,它们让人觉得readlines()返回文件中的一行列表,即整个文件都被读入内存。使用迭代器,更有可能只有文件的部分在任何时刻在内存中(在zstd的缓冲区中)。
然而,阅读文档的这一部分,我对上述内容不太确定。请继续关注...(编辑:经过实证测试,结果是正确的,请参见下文)。

附录

关于上述的注释2和注释3:我进行了实证测试,通过将行数更改为1亿,并比较了两个版本的内存使用情况(使用htop):

流式版本

with zstd.open('test.zstd', 'r') as f:
    for i, line in enumerate(f):
        if i % 10_000_000 == 0:
            print(f'line {i:8d}: {line}', end='')

--内存使用无突增。

Readlines版本

with zstd.open('test.zstd', 'r') as f:
    for i, line in enumerate(f.readlines()):
        if i % 10_000_000 == 0:
            print(f'line {i:8d}: {line}', end='')

--内存使用增加了几个GB。
可能是与安装的版本(1.5.5)有关。

如果文件是以二进制方式编写的,那么压缩数据难道不总是以二进制形式存在吗? - undefined
1
是的,请参见修改后答案中的注释1。 - undefined

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