如何部分提取压缩的巨大纯文本文件?

如何部分提取压缩的巨大纯文本文件?

我有一个大小为 1.5 GB 的 zip 文件。

它的内容是一个荒谬的大纯文本文件(60 GB),目前我的磁盘上没有足够的空间来提取所有内容,我也不想将其全部提取,即使有。

至于我的用例,如果我可以检查部分内容就足够了。

因此,我想将文件解压缩为流并访问文件的范围(就像可以通过普通文本文件的头部和尾部一样)。

要么通过内存(例如,从 32GB 标记开始提取最大 100kb),要么通过行(给我纯文本行 3700-3900)。

有办法实现吗?

答案1

请注意,gzip可以提取zip文件(至少是文件中的第一个条目zip)。因此,如果该存档中只有一个大文件,您可以执行以下操作:

gunzip < file.zip | tail -n +3000 | head -n 20

例如,提取从第 3000 行开始的 20 行。

或者:

gunzip < file.zip | tail -c +3000 | head -c 20

对于字节同样的事情(假设head支持的实现-c)。

对于档案中的任意成员,以 Unixy 方式:

bsdtar xOf file.zip file-to-extract | tail... | head...

使用head的内置函数ksh93(例如/opt/ast/bin中的when isleading $PATH),您还可以执行以下操作:

.... | head     -s 2999      -c 20
.... | head --skip=2999 --bytes=20

请注意,在任何情况下gzip//始终需要解压缩(并在此处丢弃)文件bsdtarunzip通向您要提取的部分的整个部分。这取决于压缩算法的工作原理。

答案2

一种使用 unzip -p 和 dd 的解决方案,例如提取 10kb 和 1000 个块偏移量:

$ unzip -p my.zip | dd ibs=1024 count=10 skip=1000 > /tmp/out

注意:我没有尝试使用非常大的数据......

答案3

如果您可以控制该大 zip 文件的创建,为什么不考虑使用gzip和的组合zless

这将允许您用作zless寻呼机并查看文件的内容,而无需费心提取。

如果您无法更改压缩格式,那么这显然不起作用。如果是的话,我觉得zless还是比较方便的。

答案4

我想知道是否可以做比从文件开头解压缩到这一点更有效的事情。看来答案是否定的。但是,在某些 CPU (Skylake) 上,zcat | tail不会将 CPU 提升到全时钟速度。见下文。自定义解码器可以避免该问题并节省管道写入系统调用,并且速度可能会快 10%。 (或者如果您不调整电源管理设置,Skylake 上的速度会快 60%)。


使用带有函数的自定义 zlib 可以做的最好的事情skipbytes就是解析压缩块中的符号以到达结尾,而无需执行实际重建解压缩块的工作。这可能比调用 zlib 的常规解码函数覆盖相同的缓冲区并在文件中向前移动要快得多(可能至少是 2 倍)。但不知道有没有人写过这样的函数。 (我认为这实际上不起作用,除非专门编写文件以允许解码器在某个块重新启动)。

我希望有一种方法可以跳过 Deflate 块而不对其进行解码,因为那样的话很多快点。霍夫曼树在每个块的开头发送,因此您可以从任何块的开头进行解码(我认为)。哦,我认为解码器状态不仅仅是霍夫曼树,它也是之前的 32kiB 解码数据,并且默认情况下不会跨块边界重置/忘记。相同的字节可以不断被重复引用,因此在巨大的压缩文件中可能只出现一次。 (例如,在日志文件中,主机名可能始终在压缩字典中保持“热”状态,并且它的每个实例都引用前一个实例,而不是第一个实例)。

手册zlib表示如果您希望压缩流可查找到该点,则必须Z_FULL_FLUSH在调用时使用。deflate它“重置压缩状态”,所以我认为没有它,向后引用可以进入前一个块。因此,除非您的 zip 文件偶尔使用全刷新块编写(例如每个 1G 或其他对压缩的影响可以忽略不计),否则我认为您将需要做更多的解码工作,直到您想要的程度,而不是我最初做的思维。我猜你可能无法从任何块的开头开始。


其余部分是在我认为可以找到包含所需第一个字节的块的开头并从那里解码时编写的。

但不幸的是,Deflate 块的开始并不表明它有多长,对于压缩块。不可压缩数据可以使用未压缩块类型进行编码,该块类型的前面有 16 位字节大小,但压缩块则不然:RFC 1951 对该格式的描述非常易读。具有动态霍夫曼编码的块在块的前面具有树(因此解压缩器不必在流中查找),因此压缩器必须在写入之前将整个(压缩的)块保留在内存中。

最大向后引用距离仅为 32kiB,因此压缩器不需要在内存中保留太多未压缩的数据,但这并不限制块大小。块的长度可以是数兆字节。 (这对于磁盘寻道来说足够大,即使在磁驱动器上也是值得的,而不是顺序读入内存并跳过 RAM 中的数据,如果可以找到当前块的末尾而不解析它)。

zlib 使块尽可能长: 根据马克·阿德勒的说法, zlib 仅在符号缓冲区填满时启动一个新块,默认设置为 16,383 个符号(文字或匹配项)


我对输出进行了 gzip 压缩seq(这是极其冗余的,因此可能不是一个很好的测试),但pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c在 3.9GHz、配备 DDR4-2666 RAM 的 Skylake i7-6700k 上仅以约 62 MiB/s 的压缩数据运行。解压缩数据的速度为 246MiB/s,memcpy与因块大小太大而无法放入缓存的约 12 GiB/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 限制工作到电源管理单元。)

翻译:博士:zcat | tail -c即使在快速 CPU 上也是受 CPU 限制的,除非您有非常慢速磁盘。 gzip 使用了 100% 的 CPU(根据 参考资料,每个时钟运行 1.81 条指令perf),并tail使用了 0.162 个 CPU(0.58 IPC)。除此之外,系统大部分时间处于闲置状态。

我使用的是 Linux 4.14.11-1-ARCH,默认启用 KPTI解决 Meltdown 问题,因此所有这些write系统调用gzip都比以前更昂贵:/


将搜索内置到unzipor zcat(但仍使用常规zlib解码函数)将保存所有这些管道写入,并使 Skylake CPU 以全时钟速度运行。 (这种针对某些负载的降频是 Intel Skylake 及更高版本所独有的,它们已将 CPU 频率决策从操作系统中卸载,因为它们拥有更多有关 CPU 正在执行的操作的数据,并且可以更快地升频/降频。这是通常很好,但这里会导致 Skylake 在更保守的调速器设置下无法全速运行)。

没有系统调用,只需重写适合 L2 缓存的缓冲区,直到到达所需的起始字节位置,可能至少会产生几个%的差异。甚至可能是 10%,但我只是在这里编造数字。我还没有zlib详细分析它的缓存占用量有多大,以及启用 KPTI 后每个系统调用上的 TLB 刷新(以及 uop 缓存刷新)会受到多少影响。


有一些软件项目确实向 gzip 文件格式添加了搜索索引。如果您无法让任何人为您生成可查找的压缩文件,这对您没有帮助,但其他未来的读者可能会受益。

想必这些项目都没有解码函数来知道如何在没有索引的情况下跳过 Deflate 流,因为它们仅设计为在没有索引的情况下工作可用的。

相关内容