No, boss, can you stop writing finally? So bad~

1 Introduction

Recently, I received a task. There are many large objects in the existing redis cluster (predecessors directly serialized the objects into json strings and stuffed them into redis). In order to save redis memory, it needs to be compressed with Gzip and then written to redis. It didn't sound difficult, so I wrote the following code:

image

After writing it, the expert verified it and there was no problem with the function, so he submitted the code.

2. The hidden dangers under the surface of prosperity

Some old drivers may see the problem with this code at a glance-the input and output streams are not closed . Just talk about this code: Failure to close the GZIPInputStream stream reasonably will lead to memory overflow. So how to solve it? It's very simple, just add a finally.

image

So what is the reason for this low-level problem? Some old drivers may feel that they will not make this kind of mistake, as long as they are careful to avoid it.

If you are interested, you can use any search engine to search for the usage of GZIP decompression. You will find that most of the GZIP related articles on the Internet are written in exactly the same way as my first one. So where is the problem? Maybe we should reflect on:

Many times, our focus is only on whether the function is realized, but we ignore the hidden dangers under the surface of prosperity.

3. What about the good tricks?

So how to avoid similar problems as much as possible?

Ah Sir-I understand everything you said, if only the system could automatically close the input and output streams for me!

In fact, since jdk 1.7, try-with-source syntactic sugar has supported similar functions. Let’s look at the optimized code first:

image

You can see that this code has no bulky finally code. I guess you may be thinking now: why this code does not require us to write finally code to ensure that the input and output streams are properly closed? Look directly at the compiled class file:

image

It is not difficult to see that the compiler has automatically added a finally code segment for us to release the input and output streams.

4. The universal try-with-source?

Smart, you might be murmuring again here, try-with-source looks really good, so are there any usage scenarios and restrictions on it?

You are right, there is no silver bullet for software programming. If you want to use try-with-souce to realize the automatic recovery of resources, when writing code, you need to implement the Closeable interface for the classes that have resource release.

image

Take a look at the ByteArrayOutputStream and GZIPOutputStream streams in the example I gave above. In fact, they have helped to implement the Closeable interface, so when we use it again, we can use try-with-source syntactic sugar to avoid the use of finally releasing resources. Heap of code.

 

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 {

5. Construct a class that can automatically release resources

If you still have some concerns about this, you may wish to verify it yourself:

 

public class ImageStream implements Closeable {
    public void work() {
        System.out.println("开始工作");
    }
    @Override
    public void close() throws IOException {
        System.out.println("自动释放资源");
    }
}

public static void main(String[] args) throws IOException {
    try (ImageStream is = new ImageStream()) {
        is.work();
    } catch (Exception ex) {
        System.out.println(ex.getMessage());
    }
}

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

 

开始工作
自动释放

6. Points worth noting

 

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

The above is a simple demo I wrote using try-with-source syntactic sugar. It looks very good, short and succinct. But there is actually a small trap hidden here.

We now know that after using try-with-source syntactic sugar, the close() method of GZIOutputStream will be automatically called for resource recovery. 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;
    }
}

You can see that two steps are actually performed here:

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

It is not difficult to analyze, the out here is actually the FileOutputStream object passed in by the GZIOutputStream constructor. Then the problem is coming. Look carefully, the finish method here can throw IO exceptions. If an IO exception is thrown when the finish() method is executed, then the out.close() method below is actually Will not be executed.

How to solve?

It is sufficient to declare each stream separately, as shown below:

 

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

7. Ending

The reasons for writing this article are roughly as follows:

  1. Software development can’t just focus on the realization of the function, it needs to be in awe at all times
  2. Using try-with-source syntactic sugar can indeed help us streamline the code and weaken the presence of resource release
  3. There is no silver bullet in software programming. When you understand and try to use new technologies, you should not only see its advantages, but also try to discover the hidden dangers behind prosperity and avoid stepping on pits! ! !

 

Guess you like

Origin blog.csdn.net/yunduo1/article/details/108735468