如何更优雅地关闭流(Stream)?

云中志

共 3648字,需浏览 8分钟

 · 2021-11-30

在日常开发中,我们会经常用到流(Stream),比如InputStream/OutPutStreamFileInputStream/FileOutPutStream等,你是不是经常像下面展示的这样手动关闭流,或者甚至都不关闭流呢?今天我们就来探讨下关于流关闭的问题。

通常的关闭方式

我们先看一段代码:

 /**
     * 读取文件
     */

private static void readFile() {
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream("target/classes/test.txt");
        byte[] bytes = new byte[1024];
        // 读取文件
        while (fileInputStream.read(bytes) != -1) {
            System.out.println(new String(bytes));
        }
        System.out.println(1/0);
        System.out.println(fileInputStream.available());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭资源
        if (Objects.nonNull(fileInputStream)) {
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    System.out.println("结束");
}

不符合代码质量规范

上面这段代码,从逻辑上将是ok的,但是从代码质量的角度来说是不合格的,如果你用sonar或者sonarLint插件扫描你的项目的话,它会提示你存在Code smell,也就是合理的写法:

提示信息的意思是,你应该把第24行的代码改成try-with-resources的形式。这里简单补充一下,try-with-resoucesJDK1.7引入的,目的是优化资源关闭问题,将之前try-catch-finally优化成try-catch,之前你需要手动在finally中关闭,通过try-with-resouces的方式,你再也不用手动关闭你的各种流了。

try-with-resources方式

上面的代码优化后,是这样的:

private static void readFile2() {
    try (FileInputStream fileInputStream = new FileInputStream("target/classes/test.txt")) {
        byte[] bytes = new byte[1024];
        // 读取文件
        while (fileInputStream.read(bytes) != -1) {
            System.out.println(new String(bytes));
        }
         throw new FileNotFoundException("");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

区别

相比之前代码更简洁,唯一的区别是:

try (FileInputStream fileInputStream = new FileInputStream("target/classes/test.txt")) {

就是将你需要在try代码块中用到的资源,都放进try()中,这样你的资源就会自动被关闭。这种方式其实就是一种语法糖(对用户更更友好),从代码底层来说和前面手动关闭的方式区别不大,只是之前由你写关闭,现在jdk在编译的时候,会自己加,下面反编译的代码,很好地说明了这一点:

private static void readFile2() {
    try {
      FileInputStream fileInputStream = new FileInputStream("target/classes/test.txt");
      Throwable throwable = null;
      try {
        byte[] bytes = new byte[1024];
        while (fileInputStream.read(bytes) != -1)
          System.out.println(new String(bytes)); 
        throw new FileNotFoundException("");
      } catch (Throwable throwable1) {
        throwable = throwable1 = null;
        throw throwable1;
      } finally {
        if (throwable != null) {
          try {
            fileInputStream.close();
          } catch (Throwable throwable1) {
            throwable.addSuppressed(throwable1);
          } 
        } else {
          fileInputStream.close();
        } 
      } 
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } 
  }

发现新大陆

本来事情到这里应该结束了,但是我为了搞清楚本质区别,我在close()方法上打了断点,我想看下是不是我不关闭流,它就不自己关闭。

我先试了try-with-resouces的方式,close方法被调用,这是OK的;我又试了第一段的传统写法,close也被调用了。

但是我发现,close方法都被调用了两次,而且在第一段传统写法那里,是先调了close方法,然后再进入finally执行关闭。我已经有点困惑了,但我还是想再看下不手动关闭的情况,这次比前两次更神奇,close方法竟然也被调用了,太神奇了吧!

我还在想,JDK什么时候有这个新特性的,不竟然不知道,难道和我用JDK9有关,但查了资料,发现jdk9只是支持在try-with-resources中使用final修饰的变量,也没有这个呀,这时候我看了FileInputStream的源码,发现在FileInputStream的构造方法中,这样几行代码:

 fd = new FileDescriptor();
 fd.attach(this);

也就是在创建流的时候,会把当前流加入到FileDescriptor中,FileDescriptor有一个closeAll方法,会循环关闭加入其中的流,但是这个方法也只有在FileInputStreamclose中调用呀。

我还觉得是不是正常情况下会自动关闭,但是异常情况下不会关闭,但是试了异常情况下也会走到close方法,我裂了,难道是360替我关闭了

这个问题我得再好好研究下,今天就到这里吧,温馨提示,假期余额不足

以下内容来源网络,侵删

- END -


浏览 67
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报