Better Java GZIP Compression

Better Java GZIP Compression | > /dev/null

Better Java GZIP Compression

April 30, 2010

I’ve been extending an HTTP server I maintain, and have run into a problem. To compress the response HTTP message contents I use the excellent GZIPOutputStream in the standard Java API. However, recently I have needed fine grain control over the trade-off between compression level and CPU usage on a particularly high load server. To my surprise the GZIPOutputStream class does not allow the compression level to be specified.

I set out to discover whether it was possible to customise the level of compression provided by GZIPOutputStream. The impatient can skip to the end of this post to get the answers, but the really interesting bit comes next.

First I took a look at the source code behind GZIPOutputStream. It turns out that it is little more than a Deflater in disguise:

public class GZIPOutputStream extends DeflaterOutputStream {
    ...
    public GZIPOutputStream(OutputStream out, int size) throws IOException {
        super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size);
        usesDefaultDeflater = true;
        writeHeader();
        crc.reset();
    }
    ...
}

The important part to notice in the code above is: new Deflater(Deflater.DEFAULT_COMPRESSION, true). Here a new Deflater instance is being created with a hard coded compression level of Deflater.DEFAULT_COMPRESSION. Not a very flexible design, but it does reveal that the compression level can be customised.

According to the Java API documentation the Deflater class has a method void setLevel(int level) that allows the compression level to be specified. The compression level must be between 0 and 9. A little arbitrary maybe, but along the lines of what I need (and this API was introduced in Java 1.1).

Moving up the ancestor chain I took a look at DeflaterOutputStream

public class DeflaterOutputStream extends FilterOutputStream {
    /**
     * Compressor for this stream.
     */
    protected Deflater def;
    ...
    public DeflaterOutputStream(OutputStream out, Deflater def, int size) {
        ...
        this.def = def;
        ...
    }
}

The deflater instance ends up as a variable def in the DeflaterOutputStream class. Although this is well hidden I had two things in my favour: firstly the def variable has protected access, and secondly the GZIPOutputStream is not declared final. This means that any subclass of GZIPOutputStream would have access to def. The two most practical solutions I can think of are presented below:

Instance Initialiser

One way to specify the GZIP compression level is to use the so called double brace initialisation. This is quick and easy, and useful if you only need to do it once or twice. Take a look at the following snippet:

GZIPOutputStream gzip = new GZIPOutputStream(output) {
    {
        def.setLevel(Deflater.BEST_COMPRESSION);
    }
};

In this example the first brace creates a new anonymous inner class and the second declares an instance initializer block that is run when the anonymous inner class is instantiated. This type of initializer block is formally called an instance initializer. In the example the compression is overridden to Deflater.BEST_COMPRESSION.

Subclass

The instance initialiser approach is useful when using GZIPOutputStream as a one-off, but for a more substantial solution it is better to inherit from GZIPOutputStream and create a useful subclass:

final class MyGZIPOutputStream extends GZIPOutputStream {
 
    public MyGZIPOutputStream(final OutputStream out) throws IOException {
        super(out);
    }
 
    public void setLevel(int level) {
        def.setLevel(level);
    }
}

In this example the void setLevel(int level) method can be used to directly influence the compression level after the GZIP output stream has been instantiated.

原文地址:https://www.cnblogs.com/lexus/p/2376767.html