我有一个大小为 1.5 GB 的 zip 文件。
它的内容是一个可笑的大纯文本文件(60 GB),我目前的磁盘上没有足够的空间来提取它,我也不想全部提取它,即使我有。
至于我的用例,如果我可以检查部分内容就足够了。
因此,我想将文件解压缩为流并访问文件的范围(就像可以通过普通文本文件的头部和尾部一样)。
通过内存(例如,从 32GB 标记开始提取最大 100kb)或按行(给我纯文本行 3700-3900)。
有没有办法做到这一点?
我有一个大小为 1.5 GB 的 zip 文件。
它的内容是一个可笑的大纯文本文件(60 GB),我目前的磁盘上没有足够的空间来提取它,我也不想全部提取它,即使我有。
至于我的用例,如果我可以检查部分内容就足够了。
因此,我想将文件解压缩为流并访问文件的范围(就像可以通过普通文本文件的头部和尾部一样)。
通过内存(例如,从 32GB 标记开始提取最大 100kb)或按行(给我纯文本行 3700-3900)。
有没有办法做到这一点?
注意
gzip
可以提取zip
文件(至少是文件中的第一个条目zip
)。因此,如果该存档中只有一个大文件,您可以执行以下操作:例如,提取从第 3000 行开始的 20 行。
或者:
对于与字节相同的事情(假设
head
实现支持-c
)。对于存档中的任意成员,以 Unixy 方式:
使用
head
内置的ksh93
(例如 when/opt/ast/bin
is ahead in$PATH
),您还可以执行以下操作:请注意,在任何情况下
gzip
//始终需要解压缩bsdtar
(unzip
并在此处丢弃)指向您要提取的部分的文件的整个部分。这取决于压缩算法的工作方式。一种使用 unzip -p 和 dd 的解决方案,例如以 1000 个块偏移量提取 10kb:
注意:我没有用非常大的数据尝试这个......
如果您可以控制该大 zip 文件的创建,为什么不考虑使用
gzip
和的组合zless
呢?这将允许您
zless
用作寻呼机并查看文件的内容,而不必费心提取。如果您无法更改压缩格式,那么这显然行不通。如果是这样,我觉得
zless
还是比较方便的。要查看文件的特定行,请将输出通过管道传输到 Unix 流编辑器sed。这可以处理任意大的数据流,因此您甚至可以使用它来更改数据。要按照您的要求查看第 3700-3900 行,请运行以下命令。
我想知道是否有可能做任何比从文件开头一直解压缩更有效的事情。看来答案是否定的。但是,在某些 CPU (Skylake)
zcat | tail
上不会将 CPU 提升到全时钟速度。见下文。自定义解码器可以避免该问题并节省管道写入系统调用,并且可能快约 10%。(或者,如果您不调整电源管理设置,Skylake 的速度会提高约 60%)。使用带有
skipbytes
函数的自定义 zlib 可以做的最好的事情是解析压缩块中的符号以到达结尾,而无需执行实际重建解压缩块的工作。这可能比调用 zlib 的常规解码函数覆盖相同的缓冲区并在文件中前进要快得多(可能至少 2 倍)。但我不知道是否有人写过这样的功能。(而且我认为这实际上不起作用,除非文件是专门编写的以允许解码器在某个块重新启动)。我希望有一种方法可以跳过 Deflate 块而不解码它们,因为那样会快得多。霍夫曼树在每个块的开头发送,因此您可以从任何块的开头解码(我认为)。哦,我认为解码器状态不仅仅是霍夫曼树,它也是之前的 32kiB 解码数据,默认情况下不会跨块边界重置/遗忘。相同的字节可以不断被重复引用,因此在一个巨大的压缩文件中可能只出现一次。(例如,在日志文件中,主机名可能一直在压缩字典中保持“热”,并且它的每个实例都引用前一个,而不是第一个)。
该
zlib
手册说,如果您希望压缩流可搜索到该点,则必须Z_FULL_FLUSH
在调用时使用。deflate
它“重置压缩状态”,所以我认为没有它,向后引用可以进入前一个块。因此,除非您的 zip 文件是用偶尔的全刷新块编写的(例如每 1G 或其他东西对压缩的影响可以忽略不计),否则我认为您将不得不做更多的工作来解码到您想要的点,而不是我最初思维。我猜您可能无法从任何块的开头开始。其余部分是在我认为可以找到包含所需第一个字节的块的开头并从那里解码时编写的。
但不幸的是,对于压缩块,Deflate 块的开头并不表示它有多长。不可压缩的数据可以使用前面有 16 位字节大小的未压缩块类型进行编码,但压缩块没有:RFC 1951 描述了这种格式非常可读。具有动态霍夫曼编码的块在块的前面有树(因此解压缩器不必在流中查找),因此压缩器必须在写入之前将整个(压缩的)块保存在内存中。
最大后向参考距离仅为 32kiB,因此压缩器不需要在内存中保留太多未压缩的数据,但这并不限制块大小。块可以有数兆字节长。(如果可以在不解析当前块的情况下找到当前块的结尾,那么即使在磁驱动器上,磁盘寻道也足够大,而不是顺序读入内存并跳过 RAM 中的数据)。
zlib 使块尽可能长: 根据 Marc Adler的说法,zlib 仅在符号缓冲区填满时才开始一个新块,默认设置为 16,383 个符号(文字或匹配)
我压缩了
seq
(这是极其冗余的,因此可能不是一个很好的测试)的输出,但pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c
在 Skylake i7-6700k 上以 3.9GHz 运行,压缩数据仅为 62 MiB/s,DDR4-2666 RAM。这是 246MiB/s 的解压缩数据,与memcpy
因块大小太大而无法放入缓存的 ~12GiB/s 的速度相比,这是一个巨大的变化。(
energy_performance_preference
设置为默认值balance_power
而不是balance_performance
,Skylake 的内部 CPU 调控器决定仅以 2.7GHz 运行,压缩数据约为 43 MiB /s。我sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'
用来调整它。可能如此频繁的系统调用看起来不像真正的 CPU 绑定工作到电源管理单元。)TL:DR:
zcat | tail -c
即使在快速 CPU 上也会受到 CPU 限制,除非您的磁盘非常慢。 gzip 使用了它运行的 100% 的 CPU(并且每个时钟运行 1.81 条指令,根据perf
),并tail
使用了它运行的 CPU 的 0.162(0.58 IPC)。否则,系统大多处于空闲状态。我正在使用 Linux 4.14.11-1-ARCH,它默认启用了 KPTI以解决 Meltdown,因此所有这些
write
系统调用gzip
都比以前更昂贵:/将 seek 内置到
unzip
orzcat
(但仍使用常规zlib
解码功能)将保存所有这些管道写入,并使 Skylake CPU 以全时钟速度运行。(这种针对某些负载的降频是英特尔 Skylake 及更高版本所独有的,它们从操作系统中卸载了 CPU 频率决策,因为它们有更多关于 CPU 正在做什么的数据,并且可以更快地加速/减速。这是通常很好,但这里会导致 Skylake 无法在更保守的调速器设置下全速运行)。没有系统调用,只需重写适合 L2 缓存的缓冲区,直到您到达所需的起始字节位置,可能至少会产生几个 % 的差异。甚至可能是 10%,但我只是在这里编造数字。我没有
zlib
详细分析它有多大的缓存占用空间,以及启用 KPTI 时每个系统调用上的 TLB 刷新(以及因此 uop-cache 刷新)有多少伤害。有一些软件项目确实为 gzip 文件格式添加了搜索索引。如果您无法让任何人为您生成可搜索的压缩文件,这对您没有帮助,但其他未来的读者可能会受益。
据推测,这些项目都没有解码功能,知道如何在没有索引的情况下跳过 Deflate 流,因为它们仅设计为在索引可用时工作。
您可以在 python 会话中打开 zip 文件,
zf = zipfile.ZipFile(filename, 'r', allowZip64=True)
一旦打开,您就可以打开 zip 存档中的任何文件并从中读取行等,就像它是普通文件一样。