Deflater.setLevel() 有用吗?

5

Deflater.setLevel() 对我来说效果不如预期。

static void test1() throws Exception {
     byte[] output = new byte[20];
     Deflater compresser = new Deflater();
     // compresser.setLevel(Deflater.BEST_COMPRESSION);
     compresser.setInput("blah".getBytes("UTF-8"));
     compresser.finish();
     int len = compresser.deflate(output);
     System.out.println("len="+ len+ " " +Arrays.toString(output));
}

上述对我来说可以工作正常(Java 7),但当我取消注释compresser.setLevel()行时,它就会出错(deflate()返回0字节)。除了DEFAULT之外的任何压缩级别都会发生相同的情况。更具体地说,仅当设置的级别与在构造函数中设置的级别相同时(显式或隐式地设置为此处),它才“工作”(更确切地说,是无害的)-也就是说,只有在它无用时才能使用。

Ideone中查看示例。

这个问题指向相同的问题,而被接受的答案基本上是:不要使用setter设置级别,在构造函数中设置。在我看来远非令人满意-为什么setLevel()存在?它是坏了还是我们漏掉了什么?


Deflater API似乎存在一个bug...阅读javadoc,不清楚.finish()到底是做什么的。最后一个答案中提出的解决方法可行吗? - fge
2个回答

5

我研究了一下JDK源代码,它确实设置了级别。如果你在setLevel()之后跟着compresser.deflate(new byte[0]);,那么它会起作用。

发生的事情是,在setLevel()之后的第一次调用deflate()将看到级别已更改,并调用zlib的deflateParams()函数来更改它。deflateParams()然后压缩可用的数据,但没有传递请求finish()的事实。JDK实现然后不使用Z_FINISH调用deflate()。结果,您提供的数据被发送进行压缩,压缩器累积数据,但它不会发出压缩块,因为它没有被要求完成。所以你什么都得不到。

您需要在setLevel()之后调用deflate()来实际设置级别。然后随后的数据将使用新级别进行压缩。

重要的是要注意,提供的数据直到setLevel()之后的第一个deflate()调用才会使用旧的压缩级别。只有在该deflate() 调用后提供的数据将使用新级别。因此,如果在您的示例中仅做另一个deflate()调用,则会应用finish()并获得压缩的数据,但它将使用默认的压缩级别。


你要求完成(finish())但没有传递下去,这将是一个bug,不是吗? - leonbloy
我会说是的。特别是因为我没有看到那种行为在文档中有所记录。 - Mark Adler

3
我怀疑这是一个错误。 如果您查看源代码,您会发现只有在构造函数中调用本机方法init,该方法实际上设置了压缩级别。似乎必须在任何本机init调用发生之前即在Deflater对象创建之前设置压缩级别。 setLevel(int)只是表面上为对象设置级别。没有调用本地库。

setLevel() 不仅仅是一个 NOP(在 1998 中修复),这本身就很糟糕了,更糟糕的是,它似乎会破坏解压缩。 - leonbloy
下一次调用 deflate() 设置级别。 - Mark Adler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接