【问题标题】:In PHP, how to decompress a file on the fly that was compressed twice?在 PHP 中,如何动态解压缩压缩过两次的文件?
【发布时间】:2014-09-21 02:18:38
【问题描述】:

我有一个...很大的bigfile.gz.gz 文件。我想即时解压缩它。理想情况下,这就是我的想法:

$in = fopen('compress.zlib://compress.zlib://bigfile.gz.gz', 'rb');
while (!feof($in))
    print fread($in, 4096);
fclose($in);

但是,compress.zlib:// 不能这样链接:

PHP Warning:  fopen(): cannot represent a stream of type ZLIB as a File Descriptor in gztest.php on line 1

 

所以我想我会将gzopen()compress.zlib:// 组合在一起:

$in = gzopen('compress.zlib://bigfile.gz.gz', 'rb');
while (!gzeof($in))
    print gzread($in, 4096);
gzclose($in);

但是,这只解压缩了一层 gzip。

 

我尝试了大概 10 种其他方法,不幸的是,gzopen() 不适用于 php://memory,如果它是使用 fwrite() 写入的。并且stream_filter_append(… zlib.inflate …) 无法读取压缩文件。

这是我能想到的最好的方法,但它会产生两个系统进程,这会产生不必要的开销:

$in = popen('zcat bigfile.gz.gz | gunzip', 'rb');
while (!feof($in))
    print fread($in, 4096);
fclose($in);

 

有人可以提出更好的建议吗?

【问题讨论】:

  • 为什么还要压缩两次呢? Afaik,这效率不高,因为它比第一轮后实际压缩的 CPU 成本更高。首先使用更高的压缩级别,而不是使用平均压缩级别压缩两次。
  • @DanFromGermany 因为bigfile.gz 是 302 MiB 而bigfile.gz.gz 只有 22 MiB。
  • 好吧,这出乎意料:)

标签: php gzip zlib


【解决方案1】:

可以使用 zlib.inflate 过滤器解压缩 .gz 文件。您只需要先去掉 gzip 标头。为此,您必须部署自定义过滤器:

<?php

class gzip_header_filter extends php_user_filter {

    private $filtered = 0;

    public function filter($in, $out, &$consumed, $closing) {
        while ($bucket = stream_bucket_make_writeable($in)) {
            if($this->filtered == 0) {
                $header_len = 10;
                $header = substr($bucket->data, 0, 10);
                $flags = ord($header[3]);
                if($flags & 0x08) {
                    // a filename is present
                    $header_len = strpos($bucket->data, "\0", 10) + 1;
                } 
                $bucket->data = substr($bucket->data, $header_len);
                $this->filtered = $header_len;
            }
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

stream_filter_register('gzip_header_filter', 'gzip_header_filter');

$in = fopen('bigfile.gz.gz', 'rb');
stream_filter_append($in, 'gzip_header_filter', STREAM_FILTER_READ);
stream_filter_append($in, 'zlib.inflate', STREAM_FILTER_READ);
stream_filter_append($in, 'gzip_header_filter', STREAM_FILTER_READ);
stream_filter_append($in, 'zlib.inflate', STREAM_FILTER_READ);

while (!feof($in))
    print fread($in, 4096);
fclose($in);

?>

请注意,上面的代码不处理 cmets 和其他可能存储在 gz 文件中的额外数据。

【讨论】:

  • 感谢您的回答!我想说不需要gzip_header_filter(您可以在1630 之间传递window 的值作为zlib.inflate 过滤器的参数,这将跳过gzip 标头),但是使用该过滤器实际上适用于an apparent bug in PHP
猜你喜欢
  • 1970-01-01
  • 2021-09-12
  • 1970-01-01
  • 2011-10-30
  • 1970-01-01
  • 2014-03-28
  • 2011-11-15
  • 2019-12-06
  • 2013-10-30
相关资源
最近更新 更多