2023年1月

如何用stream filter直接解压gzip格式的压缩流

PHP 提供了流过滤器功能,可以直接在流的读写过程中透明地进行加密、压缩、计算校验和等操作。然而压缩过滤器的官方文档上只介绍了 zlib.deflatezlib.inflate 两个过滤器可以处理 zlib 压缩的数据流,没有提供直接处理 gzip 格式压缩流的方法。

上一篇文章中我分享了使用 DEFLATE 算法的三种压缩格式,可以知道 zlib 和 gzip 使用的都是 DEFLATE 数据压缩算法,只是头尾长度和内容不同而已。这里我再分享一下直接用 zlib 过滤器处理 gzip 压缩流的方法,毕竟流过滤器有着不必接触原始文件,直接进行数据处理的优势。

方法非常简单,只要在挂载 zlib 流过滤器时,提供参数 window=31 即可。以下是范例代码,我们从 S3 中读取 gzip 压缩过的 CSV 文件流:

$stream = $s3_client->getReadStream('somefile.csv.gz');
stream_filter_append($stream, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 31]);
$line = fgets($stream);
if (trim($line) !== '') {
    $fields = str_getcsv($line);
    // ...
}

根据 PHP 的官方文档,这个 window 参数的值应该在 8 到 15 之间才对,表示压缩时的窗口大小为 28 到 215 之间,为什么可以指定为 31 呢?这可以算是 zlib 库的一种高级用法。根据 zlib 库的文档windowBit 可以介于 8..15,意思是窗口大小在28 到 215 之间,也可以指定 -8..-15 表示要输出没有 zlib 封装的 DEFLATE 原始数据,或者还可以加上 16 来改用 gzip 封装格式。zlib 库中的 windowBit 正好对应了 PHP stream_filter_append$params 参数里的 window 项目。

可能是因为 zlib 文档中声明这些高级用法是只针对当前版本有效,所以 PHP 官方文档没有将这些特殊值收录进来。不过至少当前版本我们可以放心使用,可以在 composer.json 中的 platform 部分声明一下兼容的 ext-zlib 版本为 ^1.0,这样版本有变化的时候,composer 会给予提示。

PHP该用哪个压缩方法gzdeflate、gzcompress、gzencode

Gzip 是一种非常常见的压缩格式,PHP 也有一系列 gz 开头的函数用于操作 gzip 格式的压缩文件。然而令人困惑的是,同样是做压缩处理,却有着 gzdeflategzcompressgzencode 三个函数可用。那么它们都有着什么区别,平时又究竟应该用哪个呢?

这三个函数使用的压缩算法是一样的,都是 DEFLATE 压缩算法,三者的区别仅在于数据的封装格式不同。

  • gzdeflate 函数输出的是 DEFLATE 算法生成的原始数据,也被称作 RAW 格式的数据。由 RFC1951 定义
  • gzcompress 函数输出的是 zlib 格式的数据,它在 DEFLATE 生成的原始数据开头增加了两个字节的 header,末尾增加了四个字节的 ADLER-32 校验和。由 RFC1950 定义
  • gzencode 函数输出的是 gzip 格式,它是 gzip 工具定义的文件存储格式,理论上讲允许使用不同的压缩算法,但实际上目前只有 DEFLATE 一种实现。gzip 格式在 DEFLATE 生成的原始数据开头加入了一个至少10字节的变长header,末尾加入了固定8个字节的 tailer。由 RFC1952 定义

了解了三个函数的区别,就可以根据需要选择具体用哪个函数了。

  • gzdeflate 适用于可靠数据传输和存储环境下,需要减少数据量的情况。比如把数据压缩后存入 redis 或者 MySQL 的 blob 字段。
  • gzcompress 适用于需要处理 zlib 格式数据的场景。比如向 Accept-Encoding: deflate 的浏览器输出压缩的数据流。
  • gzencode 适用于需要被 gzip 工具解压的情况。比如把数据压缩并保存到 OSS 上,命名为 xxx.gz 的文件,允许用户下载后自行解压。

对了,PHP 还有一个 zlib_encode 函数,允许开发人员指定 $encoding 参数,它可以是 ZLIB_ENCODING_RAWZLIB_ENCODING_DEFLATE 或者 ZLIB_ENCODING_GZIP 三者之一,正好对应了 gzdeflategzcompressgzencode 三个函数的效果。如果读代码的人怕搞混,不妨考虑直接使用这个函数。