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:

image

After writing, the traveler verifies that the functionality is fine and commits the 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.

image

So what is the cause of this low-level problem? Some older drivers may think they won’t make such mistakes and can avoid them as long as they are careful.

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.

image

You can see that this code is no longer cumbersome finally code. Now I guess you’re probably wondering: why doesn’t this code require us to write finally code to ensure that the input and output streams close correctly? Look directly at the compiled class file:

image

It’s not hard to see that the compiler has automatically added a finally section for us to release the input and output streams.

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.

image

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 (" implements "); } @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:

 

Start work automatically releasedCopy 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() throws IOException { if (! closed) { finish(); if (usesDefaultDeflater) def.end(); out.close(); closed = true; } } public void close() 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!!