• 2021.05.02 更优雅地关闭流(Stream)


    在日常开发中,我们会经常用到流(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替我关闭了?

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

    以下内容来源网络,侵删

  • 相关阅读:
    组策略导入导出secedit
    ipad常见错误
    ipad系统路径
    内核操作注册表例子
    WoW64子系统
    win2003 shutdown命令
    regedit 导入注册表
    windbg for CLR
    WM_POWERBROADCAST
    OpenSSL命令
  • 原文地址:https://www.cnblogs.com/caoleiCoding/p/14727107.html
Copyright © 2020-2023  润新知