【问题标题】:Calling java.util.zip.Deflater.setInput multiple times does nothing after first time在第一次之后多次调用 java.util.zip.Deflater.setInput 什么都不做
【发布时间】:2017-02-15 17:34:24
【问题描述】:

我正在使用 java.util.zip.Deflater 来压缩通过 HTTP PUT 接收的传入数据流,这意味着我将数据压缩成点点滴滴,而不是一次全部压缩。因此,当数据进入时,我的 HTTP 请求处理程序重复调用如下所示的方法:

byte[] compress(byte[] input) {
    byte[] output = null;
    if (this.compressor == null) {
        this.compressor = new Deflater(Deflater.BEST_COMPRESSION, true);

        // Add gzip header:
        output = getGzipHeader();
    }

    this.compressor.setInput(input);
    this.compressor.finish();

    while (!this.compressor.finished()) {
        byte[] tempOutput = new byte[10240];

        int compressedLength = this.compressor.deflate(tempOutput);

        if (output == null) {
            output = Arrays.copyOf(tempOutput, compressedLength);
        } else {
            byte[] newOutput = Arrays.copyOf(output, output.length + compressedLength);
            System.arraycopy(tempOutput, 0, newOutput, output.length, compressedLength);
            output = newOutput;
        }
    }

    // Update CRC:
    this.crc.update(input);
    this.byteCount += input.length;

    return output
}

当然,包含这个方法的类也有实例变量:

private Deflater compressor;
private CRC32 crc = new CRC32();
private long byteCount = 0;

一旦从 HTTP 请求接收到最后一个字节,我会从 crcbyteCount 实例变量中附加 CRC 和总未压缩长度。

只要我在 HTTP PUT 中发送非常少量的数据,就可以很好地工作,因为 compress 方法只被调用一次。我最终得到了一个有效的 gzip 文件。一旦我发送超过几百个字节,导致compress 被多次调用,它就不起作用,因为在第一个之后对compress 的所有后续调用中,this.compressor.finished() 返回 true,即使我用新的输入数据调用了this.compressor.setInput(input)。如果我在处理完所有数据后查看this.compressor.getBytesRead(),则该调用返回的值正是传入的第一个输入缓冲区的大小(第一次调用this.compressor.setInput(input))。对该方法的后续调用都不会增加getBytesRead() 返回的值。

如果我在调用setInput() 之后不调用finish(),它根本不起作用——我没有得到任何输出。但似乎调用finish() 是在告诉Deflater 不要接受任何更多的输入。

我做错了什么?

【问题讨论】:

  • 这有帮助吗:DeflaterOutputStream? (直接使用这个类有意义吗?)
  • 正如 jtahlborn 所说,DeflaterOutputStream 会有很大帮助。作为一般性评论,如果您使用 ArrayList.addAll(Arrays.asList(tempOutput)) 而不是自己进行所有数组操作,您的代码看起来会简单得多。
  • @jtahlborn,不,据我所知,我无法使用 DeflatorOutputStream,因为我没有要写入的输出流。我可能应该提到我的服务是使用Vert.x 构建的,所以我从ReadStream 读取并写入AsyncFile。数据从字节缓冲区中的 ReadStream 接收并写入字节缓冲区中的 AsyncFile。没有输入或输出流。
  • @KlitosKyriacou,不,这将添加整个 tempOutput 缓冲区,包括所有 0 字节。我正在为手头的问题寻求帮助,能够基于读取输入数据流中的小块来编写压缩数据——我对一般的编码建议不感兴趣,谢谢。此代码已针对此帖子进行了简化,以试图清楚地说明具体问题。
  • 您可以实现一个写入 AsyncFile 的 OutputStream。

标签: java http gzip


【解决方案1】:

问题解决了。我基本上将DeflaterOutputStream.write(...)中的代码几乎逐字复制到我的compress方法中,并从DeflaterOutputStream.close()复制到我自己的finish方法中,效果很好。

这里的技巧在 Deflater 的 javadoc 中甚至没有解释(实际上与该 javadoc 中显示的示例代码相矛盾)是,在接收输入并调用 deflater.setInput(...) 时,您只检查!deflater.needsInput(),而不是!deflater.finished()。然后只有在收到所有输入后,您才调用deflater.finish(),然后至关重要的是,您可以通过循环while (!deflater.finished()) 并继续对任何剩余数据进行放气来处理Deflater 缓冲区中可能未决的任何剩余数据。

有关详细信息,只需打开 DeflaterOutputStream 的源代码并查看其 write(byte[] b, int off, int len)finish() 方法即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多