Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Deflater.setLevel() work?

Tags:

java

deflate

Deflater.setLevel() does not work as expected for me.

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));
}

The above works ok for me (Java 7), but when I uncomment the compresser.setLevel() line, it breaks (deflate() returns 0 bytes). The same happens with any compression level, except for DEFAULT. More specifically, it only "works" (rather, it's harmless) when the level set is the same that was set (explictly or implicitly, as here) in the constructor - that is to say, it only can be used when it's useless.

See an example at Ideone.

This question points to the same problem, and the accepted answer basically says: do't set the level with the setter, do it in the constructor. Far from satisfactory, IMO - why does setLevel() exists? Is it broken or are we missing something?

like image 761
leonbloy Avatar asked Jul 13 '13 17:07

leonbloy


2 Answers

I dug a little into the JDK source code. It does indeed set the level. If you follow the setLevel() with compresser.deflate(new byte[0]);, then it will work.

What's happening is that the first call of deflate() after the setLevel() will see that the level is changed, and call zlib's deflateParams() function to change it. deflateParams() will then compress the available data, but the fact that you asked for a finish() is not passed along. The JDK implementation then does not call deflate() with Z_FINISH. As a result, the data you gave is sent for compression, the compressor accumulates the data, but it does not emit a compressed block since it was not asked to finish. So you get nothing.

You need the call to deflate() after the setLevel() to actually set the level. Then subsequent data will be compressed with the new level.

It is important to note that data provided up to the first deflate() call after the setLevel() will be compressed with the old compression level. Only data provided after that deflate() call will use the new level. So if in your example you simply did another deflate() after the last one, it would apply the finish() and you would get your compressed data, but it would use the default compression level.

like image 146
Mark Adler Avatar answered Sep 28 '22 06:09

Mark Adler


I suspect this is a bug.

If you look into the source code, you will find that only in the constructor do they call the native method init which actually sets the level of compression. It seems that the compression level must be set before any of the native init calls take place .ie Before the creation of the Deflater object.

The setLevel(int) just sets the level for the object superficially. There is no call to the native library.

like image 25
bsd Avatar answered Sep 28 '22 06:09

bsd