美文网首页Java架构技术进阶
不是吧,阿Sir啊,可不可以不用再写finally?

不是吧,阿Sir啊,可不可以不用再写finally?

作者: 代码小当家 | 来源:发表于2020-07-13 15:31 被阅读0次

1、引言

最近行者接到了一个任务,现有 redis 集群存在比较多的大对象(前人直接将对象序列化成 json 字符串后塞到 redis 中),为了节约 redis 内存,需要用 Gzip 压缩后再写入 redis。听起来没啥难度,于是三下五除二写下了如下代码:

image

写完之后,行者验证了一把,功能也没啥问题,于是就提交了代码。

2、繁荣表面下的隐患

有些老司机可能一眼就看出这段代码的问题了——输入输出流没有关闭。单论这段代码:没有合理的关闭 GZIPInputStream 流会导出内存溢出。那么如何解决呢?很简单,加个 finally 就可以了。

image

那么发生这种低级问题原因是什么呢?有些老司机可能觉得自己不会犯这种错误,只要自己细心就能避免。

如果大家感兴趣,可以用任意搜索引擎搜一下 GZIP 解压缩的用法。你会发现:网上大部分 GZIP 相关文章中的写法和我第一种写法如出一辙。那么问题到底出在了哪里?也许我们应该反思下:

很多时候,我们的关注点只有功能有没有实现,却忽视了繁荣表面下的隐患

3、说好的奇技淫巧呢?

那么如何尽量避免类似问题的发生呢?

阿Sir啊——你说的我都懂,要是系统能自动帮我关闭输入输出流就好了!

实际上自从 jdk 1.7 开始,try-with-source 语法糖就已经支持了类似功能,先看优化后的代码:

image

可以看到这段代码已经没有了笨重的 finally 代码了。我猜你现在可能在想:为啥这段代码的不需要我们写 finally 代码来保障输入输出流的正确关闭呢?直接看编译后的 class 文件:

image

不难看出编译器已经自动帮我们添加了 finally 代码段来释放输入输出流。

4、万能的 try-with-source?

聪明的你看到这里心里可能又在犯嘀咕了,try-with-source 看起来的确很不错, 那么它有没有啥使用场景、限制条件的呢?

你说的没错,对于软件编程来说没有银弹。如果要借助 try-with-souce 来实现资源的自动回收,在编写代码的时候,针对有资源释放的类需要实现 Closeable 接口。

image

再看看我上面给出示例中的 ByteArrayOutputStream 和 GZIPOutputStream 流,其实已经帮实现了 Closeable 接口,所以我们再使用的时候,我们借助 try-with-source 语法糖就能免去使用 finally 释放资源的那一堆代码了。

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、构造可自动释放资源的类

如果你对此还有一些顾虑,不妨自己动手验证一番:

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

这是的一个简单测试类,实现了 Closeable 方法。运行结果如下:

开始工作
自动释放

6、值得注意的地方

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

上面是我使用 try-with-source 语法糖写的一个简易 demo,看起来很不错,短小精悍。但其这里其实隐藏着一个小陷阱。

我们现在已经知道,用 try-with-source 语法糖后,GZIOutputStream 的 close() 方法会被自动调用进行资源回收。我们来看下 GZIOutputStream 的 close() 方法

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

可以看到这里实际上执行了两步操作:

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

不难分析,这里的 out 实际上是 GZIOutputStream 构造方法传入的 FileOutputStream 对象。那么问题就来了,各位大佬仔细看,这里的 finish 方法是可以抛出 IO 异常的,如果在执行 finish() 方法时,抛出了 IO 异常,那么下面的 out.close() 方法实际上不会被执行的。

如何解决?

单独声明每个流就可以了,如下所示:

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、尾声

为啥写这篇文章,原因大致如下:

  1. 软件开发不能只关注功能实现,需要时刻保持一颗敬畏的心
  2. 使用 try-with-source 语法糖确实可以帮助我们精简代码并且弱化资源释放的存在感
  3. 软件编程没有银弹,在了解并尝试使用新技术的时候,不仅仅要看到它的优势,也要尝试去发现繁荣背后的隐患,避免踩坑啊!!!

相关文章

  • 不是吧,阿Sir啊,可不可以不用再写finally?

    1、引言 最近行者接到了一个任务,现有 redis 集群存在比较多的大对象(前人直接将对象序列化成 json 字符...

  • Vue 怎么实现导出Excel表格

    喂!不是吧阿sir,这导出表格也可以这么详细 1.安装依赖 npm install -S file-saver x...

  • 不是吧阿sir,拉shi你也看?

    不知道为什么,铲shi的总会想方设法从主子身上获得各种各样独特的愉悦感。 其中令主子们尴尬无比的,就是拉粑粑被人盯...

  • 熟鸡蛋

    来自小女孩的震惊,不是吧,阿Sir。现在外面中午温度40几度?你告诉我怎么出门?你告诉我怎么出门? 现在正式开讲 ...

  • 不是吧阿sir,2020还有人玩页游!

    不仅有,还有10款巨火的: 当你想玩游戏的时候,通常会怎样选择一款游戏? 想必大家都跟小5一样,会从最火的游戏中挑...

  • 啊~FINALLY

    终于在昨天结束了漫长的数据库阶段,在昨天下午结束了本学期的篮球赛季,感觉这个寒假比较有意义的还是篮...

  • 山野札记――我

    我是谁? 仅仅是别人口中的高大图? 不是吧!阿Sir,怎么会这么简单呢!? 我是由千千万万个我组成的 包括你口中的...

  • 不是吧 阿sir 让我买个票这么难吗

    一、个人操作 还是空仓 二、热门板块 今天煤炭成了龙头,这个是有逻辑的,各地限电停电。但这里面9支涨停,一个符合条...

  • 阿楠、小岁、妈咪、啊sir、洋洋……

    啦啦啦~大家好呀,这篇文章是“我的朋友都是宝藏”系列发表的第一篇!感谢大家的阅读和关注!(不是无中生友系列哈) 该...

  • 幸会啊,sir

    天色沾染墨汁 月光打湿眼眶 自古烦忧惹纷扰 只顾晕开 兜售心寒 去日苦多 泪洒两行 悲伤未央 自难相忘 却低眉把先...

网友评论

    本文标题:不是吧,阿Sir啊,可不可以不用再写finally?

    本文链接:https://www.haomeiwen.com/subject/xcnacktx.html