在只读 gzip 流中实现查找

use*_*114 5 python gzip zlib python-3.10

.gz我有一个在类似文件的对象中查找的应用程序。

\n

Pythongzip.GzipFile支持这一点,但是效率很低 \xe2\x80\x93 当 GzipFile 对象被要求向后查找时,它将倒回到流的开头 ( seek(0)),然后读取所有内容并将其解压缩到所需的偏移量。

\n

不用说,当寻找一个大的数据时,这绝对会降低性能。tar.gz文件(数十 GB)时,这绝对会降低性能。

\n

因此,我希望实现检查点:时不时地存储流状态,当要求向后查找时,仅转到下一个先前存储的检查点,而不是一直倒回到开头。

\n

我的问题是围绕gzip/zlib实现:“当前解压缩器状态”由什么组成?它存储在哪里?它有多大?

\n

如何从打开的 GzipFile 对象中复制该状态,然后将其分配回“向后跳转”搜索?

\n

注意我无法控制输入 .gz 文件。解决方案必须严格针对只读rb模式的GzipFile。

\n
\n

编辑:查看 CPython 的源代码,这是相关的代码流和数据结构。从顶层 (Python) 到原始 C 排序:

\n
    \n
  1. gzip.GzipFile._buffer.raw

    \n
  2. \n
  3. gzip._GzipReader

    \n
  4. \n
  5. gzip._GzipReader.seek() == DecompressReader.seek() <=== 需要更改此设置

    \n
  6. \n
  7. ZlibDecompressor 状态+其深层复制 <=== 需要复制/恢复此内容

    \n
  8. \n
  9. z_stream结构

    \n
  10. \n
  11. 内部状态结构

    \n
  12. \n
\n
\n

EDIT2:还在以下位置找到了这个预告片zlib

\n
\n

通过保存该块的起始文件偏移量和位以及该块之前的 32K 字节未压缩数据,可以在任何 deflate 块的开头创建访问点。此外,该块的未压缩偏移量也被保存,以提供在未压缩流中定位所需起始点的参考。

\n
\n
\n

构建索引的另一种方法是使用 inflateCopy()。这不会被限制为在块边界处具有访问点,但每个访问点需要更多内存,并且由于在状态中使用指针而无法保存到文件。

\n
\n

(他们称之为“接入点”,我称之为“检查点”;同样的事情)

\n

这几乎回答了我所有的问题,但我仍然需要找到一种方法来翻译这个zran.c示例以使用 CPython 中的 gzip/zlib 脚手架。

\n

Nic*_*ell 4

您可以尝试一个名为indexed_gzip 的库,它构建在 zlib 的zran.c实用程序之上。本质上,这个库在整个文件中保留了一系列检查点,当对特定字节偏移量的请求到达时,它从最近的检查点开始。(indexed_gzip 将此称为“索引查找点”。)

文档中的用法示例:

import indexed_gzip as igzip

# You can create an IndexedGzipFile instance by specifying a file name.
myfile = igzip.IndexedGzipFile('big_file.gz')

# Or by passing an open file handle. In this use, the file handle
# must be opened in read-only binary mode:
myfile = igzip.IndexedGzipFile(fileobj=fileobj, auto_build=True, spacing=1024**2)

# Write support is currently non-existent.

Run Code Online (Sandbox Code Playgroud)

auto_build模式(True默认情况下)允许增量索引构建:每次调用都会seek(offset)扩展检查点索引以包含偏移量,以防尚未覆盖。

您可能需要调整该spacing参数,该参数控制未压缩文件内容中每个检查点之间的字节数。这是时间与内存的权衡:更多的检查点意味着每次查找时需要完成的工作更少,但这意味着更多的内存用于检查点。默认为 1MB 的未压缩数据。

为了更快地启动,您可以将索引写入磁盘(该索引比底层压缩文件小得多)并在下次程序运行时加载该索引。有关如何使用此功能的更多信息,请参阅索引导入/导出。