文章较长, 建议收藏观看
目录
Automatic Resource Management (ARM)
*手搓代码, 写一个通用的函数, 写成静态的当成工具类便于调用
前言:
关闭IO流等是我们操作文件的基本操作
JVM只会帮我们回收堆栈中的内存, 而对于IO流这种物理连接它无能为力, 得我们手动释放
如果不释放可能 会导致内存泄漏
1.基本关闭方法
大家平时关闭流可能是这么做的
try{
FileInputStream fis = new FileInputStream("hello.txt");//读文件的流
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
// 你要做的操作
}
// !!! 不建议的写法 !!!
fis.close();
}catch (IOException e) {
e.printStackTrace();
}
在close()关闭流对象之前
比如说fis.read(buffer)这一行出错了
抛出的异常就会被catch语句捕获, 不再执行fis.read(buffer)下面的语句
这意味着, 我们的流并没有关闭 !
怎么解决呢?
有些同学可能会说, 那我在catch语句里面再写一次 fis.close() 不就得了
这显然不够优雅, 也不符合代码复用的原则
我们的解决方法是这样的
异常处理中, try catch finally是常见的结构
finally代码段, 是异常处理的"善后"操作, 不论是否发生异常, 最后都会执行(finally代码段)
我们把关闭资源的操作, 放到finally代码段即可
上代码
改进版本
FileInputStream fis = null;//读文件的流
try {
fis = new FileInputStream("hello.txt");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
// 你要做的操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
乍一看, 还复杂了, 其实都是很有必要的操作, 我们一行行分析
分析代码:
首先看finally代码段
程序可能会在new IO流对象的时候出错,
if (fis != null)这里判断有没有生成IO流的对象,如果为null,说明还没有生成IO流对象, 也就不用关闭
finally {
//程序可能会在new IO流对象的时候出错,
//if (fis != null)这里判断有没有生成IO流的对象,如果为null,说明还没有生成IO流对象
//也就不用关闭
if (fis != null) {
try { //这里用到try语句是因为close()本身就会抛异常,正常处理即可
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
再看看开头的代码
FileInputStream fis = null;//读文件的流
try {
fis = new FileInputStream("hello.txt");
这里为什么看着这么奇怪?
拿着一个空的对象进去try代码段再赋值
因为, 我们的IO流资源 最终是要在finally里面关闭的,
如果只是像下面这样写
try{
FileInputStream fis = new FileInputStream("hello.txt");//读文件的流
finally里面 就访问不到 , 就类似于全局变量的意思
(如果实在理解不了的同学, 可以把代码写在try里面试试, 行不通)
到这里, 我们可以说是写出了 能容纳一定错误的代码
2.进阶关闭方法try-with-resources
上面的代码已经看着近乎完美了, 为什么还有个 2.0呢?
我们举例子
如果我们在项目中要用到这么多 流 , 都要一个个去关闭, 即使有IDEA代码补全, 是不是也有点崩溃?
finally {
if (ps != null) {
try {
ps.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
那我们该怎么办呢?
try (InputStream is = new FileInputStream("test")) {
is.read();
...
} catch(Exception e) {
...
} finally {
//no need to add code to close InputStream(笔者注:不需要添加关闭InputStream的代码)
//it's close method will be internally called(笔者注:它的关闭方法将被内部调用)
}
这意味着什么?
流资源在使用完成时将自动被关闭.
还是不太理解?
我们看代码
上文的代码:
FileInputStream fis = null;//读文件的流
try {
fis = new FileInputStream("hello.txt");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
// 你要做的操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-----进阶代码-----
try (FileInputStream fis = new FileInputStream("hello.txt")) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
// 你要做的操作
}
} catch (IOException e) {
e.printStackTrace();
}
这对比我们上文给出的代码, 是不是优雅了许多?
我们无需添加关闭InputStream输入流的代码,其close()方法会被自动调用
3.最终版本Pro Plus Max(手搓代码)
??? 还能写??
是的, 这是我前段时间做项目总结出来的
如果上面那个方法真的完美, 就不会有我现在的最终版本Pro Plus Max了(笑)
DataOutputStream dos1 = null;//写文件到本地的
try (DataInputStream dis1 = new DataInputStream(socket.getInputStream()))//获取socket套接字{
String fileName = dis1.readUTF();//1.从socket读取文件名
File file = new File(fileName);
dos1 = new DataOutputStream(new FileOutputStream(file));//写文件的流
...
}
......(由于它是在半路创建的流,所以最后只能用常规方法来释放)
if (t != null) {
try {
t.close();
} catch (Exception e) {
e.printStackTrace();
}
}
上面服务器的代码逻辑就是:
0.客户端给服务器传输文件, 服务器接收(当然包括文件名等必要信息)
1.服务器接收到客户端传来的文件名
2.服务器根据文件名才能 new 一个OutputStream流对象
那么上文所述-----进阶代码-----
的缺陷就来了:
如果我有一个读取流和写出流, 都想放到try 代码段里是做不到的......
因为写出流outputstream的创建, 要依靠读取流读inputstream取文件名的操作来完成
由于它是在半路创建的流,所以最后只能用常规方法来释放
难受了, 又绕回来了,难道真的没有办法避免了吗?
先不急
我们进入IDEA 查看 FileOutputStream 类
右键显示关系图
我们可以看到这么个关系
重点关注左边的方法 咦? closeable 是不是和close有关系 ?
果然, close()方法就是从这里来的...
第一处翻译:
第二处翻译:
它还有一个父亲 AutoCloseable, 我们来看看
我们看下翻译:
意味着, 我们JDK7.0以后, 流资源的关闭, 都和AutoCloseable挂钩了
说得再通俗一点:
好的, 那话不多说,
手搓代码, 写一个通用的函数, 写成静态的当成工具类便于调用
其中的函数用到了泛型, 意味传进来的参数是实现了AutoCloseable接口的
public class NetFunction {
public static <T extends AutoCloseable> void closeStream(T t) {
if (t != null) {
try {
t.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这样, 即使流放不进try里面, 也可以愉快的关闭啦.
什么?你还想再少一点...?
把所有的东西都放进去一起关闭?
比如说
NetFunction.closeStream(dos1,dos2,socket);
额, 那就再改改代码
批量关闭传进去的流对象
public static <T extends AutoCloseable> void close(T... ts) {
for (T t : ts) {
if (t != null) {
try {
t.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
怎么样, 是不是满足了所有要求了?
可以批量关闭传进去的流对象
是的, 但是我看编译器的时候强迫症犯了...
百度了一下
额, 大概就是 使用泛型的时候用可变形参会导致这个问题发生, 加个注解即可
但是我强迫症, 加个注解显然不优雅, 又想消除这一警告
最后沉吟许久, 想到了Java基础知识里面的转型...
最终版本Pro Plus Max
public static void close(AutoCloseable... t) {
for (AutoCloseable closeable : t) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
NetFunction.close(dos1,dos2,socket);
如果还有更好的办法, 欢迎在评论区指出