如何更优雅地关闭流(Stream)?
共 3648字,需浏览 8分钟
·
2021-11-30 07:35
在日常开发中,我们会经常用到流(Stream
),比如InputStream/OutPutStream
、FileInputStream/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-resouces
是JDK1.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
方法,会循环关闭加入其中的流,但是这个方法也只有在FileInputStream
的close
中调用呀。
我还觉得是不是正常情况下会自动关闭,但是异常情况下不会关闭,但是试了异常情况下也会走到close
方法,我裂了,难道是360替我关闭了?
这个问题我得再好好研究下,今天就到这里吧,温馨提示,假期余额不足
以下内容来源网络,侵删
- END -