1, the introduction

Recently, walker received a task that there are a lot of large objects in the existing REDis cluster (previous people directly serialized the objects into JSON strings and stuffed them into Redis). In order to save redis memory, it is necessary to compress the objects with Gzip before writing them into Redis. That didn’t sound too difficult, so I wrote the following code:

Hidden dangers beneath the surface of prosperity

Some older drivers might immediately recognize the problem with this code — the input/output stream is not turned off. This code alone: not properly closing the GZIPInputStream will result in an overflow of memory. So how to solve it? It’s easy. Finally.

If you are interested, you can search any search engine for GZIP decompression. You’ll notice that most of the GZIP articles on the web are written in exactly the same way I wrote them first. So what went wrong? Maybe we should reflect:

Too often, we focus only on the functionality, but ignore the hidden dangers beneath the surface of prosperity

3. What about the strange tricks?

So how do you try to avoid similar problems?

Sir Ah – you said I understand, if only the system can automatically help me shut down the input and output stream!

In fact, try-with-source syntax sugar has supported similar functionality since JDK 1.7.

4. Try-with-source?

If you’re smart enough to be wondering, try-with-source looks great, but are there any usage scenarios or restrictions?

You are right, there is no silver bullet when it comes to software programming. If you want to implement automatic resource collection with try-with-souce, you need to implement the Closeable interface for classes with resource release when writing the code.

Looking at the ByteArrayOutputStream and GZIPOutputStream streams in the example I gave above, we already implement the Closeable interface, so when we use them again, We can use the try-with-source syntax to save a lot of code from using finally to release resources.

public class ByteArrayOutputStream extends OutputStream {
public abstract class OutputStream implements Closeable, Flushable {

public class GZIPOutputStream extends DeflaterOutputStream {
public class DeflaterOutputStream extends FilterOutputStream {
public class FilterOutputStream extends OutputStream {
public abstract class OutputStream implements Closeable, Flushable {
Copy the code

Construct classes that automatically free resources

If you have any doubts about this, check it out for yourself:

public class ImageStream implements Closeable {
    public void work() {
        System.out.println("Get to work");
    }
    @Override
    public void close() throws IOException {
        System.out.println("Automatically release resources"); } } public static void main(String[] args) throws IOException { try (ImageStream is = new ImageStream()) { is.work(); } catch (Exception ex) { System.out.println(ex.getMessage()); }}Copy the code

This is a simple test class that implements the Closeable method. The running results are as follows:

Automatically releases resources when workingCopy the code

6. Something worth noting

try (
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(createdFile));
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
} catch (Exception ex) {
    / /...
}
Copy the code

Above is a simple demo I wrote using try-with-source syntax sugar. It looks good and short. But there’s a little catch.

We now know that with try-with-source sugar, the close() method of GZIOutputStream is automatically called for resource collection. Let’s look at the close() method of GZIOutputStream

public void close(a) throws IOException {
    if(! closed) { finish();if (usesDefaultDeflater)
            def.end();
        out.close();
        closed = true; }}public void close(a) throws IOException {
    if(! closed) { finish();if (usesDefaultDeflater)
            def.end();
        out.close();
        closed = true; }}Copy the code

You can see that there are actually two steps:

  1. finish( )
  2. out.close( )

Out is actually a FileOutputStream object passed in by the GZIOutputStream constructor. If an IO exception is thrown when the finish() method is executed, the out.close() method will not be executed.

How to solve it?

Declare each stream separately, as follows:

try (
    OutputStream out = new FileOutputStream(createdFile);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(out);
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
} catch (Exception ex) {
    / /...
}
Copy the code

7, the end

The reasons for writing this article are as follows:

  1. Software development needs to be more than just functional. It needs to be in awe at all times
  2. Using try-with-source syntactic sugar can really help simplify code and reduce the presence of resource release
  3. There is no silver bullet in software programming. When learning and trying to use a new technology, not only should you see its advantages, but you should also try to find the hidden dangers behind the boom and avoid the pitfalls!!