文章较长, 建议收藏观看
目录
1.基本关闭方法
上代码
分析代码:
2.进阶关闭方法try-with-resources
Automatic Resource Management (ARM)
3.最终版本Pro Plus Max(手搓代码)
*手搓代码, 写一个通用的函数, 写成静态的当成工具类便于调用
*批量关闭传进去的流对象
*最终版本Pro Plus Max
前言:
关闭IO流等是我们操作文件的基本操作
JVM只会帮我们回收堆栈中的内存, 而对于IO流这种物理连接它无能为力, 得我们手动释放
如果不释放可能 会导致内存泄漏
大家平时关闭流可能是这么做的
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();
}
Q: 为什么直接 fis.close() 不建议呢?
A: 因为这么写, 程序不具有健壮性
在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.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-with-resources - Javapapers
Automatic Resource Management (ARM)
自动资源管理(ARM)
In Java 7, we got a nice feature to manage these resources automatically. Manage is really a hype word here, all it does is close the resources. Automatic resource management, helps to close the resources automatically.
在Java 7中,我们有一个很好的功能来自动管理这些资源。管理在这里真的是一个炒作的(笔者注: 类似于炒作)词,它所做的只是关闭资源。自动资源管理,有助于自动关闭资源。
Resource instantiation should be done within try(). A parenthesis () is introduced after try statement and the resource instantiation should happen within that paranthesis as below,
资源实例化应该在try()中完成。在try语句后面有一个小括号(),资源实例化应该发生在这个小括号内,如下所示。
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();
}
}
}
-----进阶代码-----
直接把IO对象的创建放在try语句的括号里面,让Java帮我们管理它的释放
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()方法会被自动调用
??? 还能写??
是的, 这是我前段时间做项目总结出来的
如果上面那个方法真的完美, 就不会有我现在的最终版本Pro Plus Max了(笑)
我写的是TCP文件传输程序, 源代码见我仓库:mobeiCanyue/FileMaster: Java实现TCP传输文件或消息,局域网无忧 (github.com)
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()方法就是从这里来的...
第一处翻译:
一个Closeable是一个可以被关闭的数据源或目的地。
第二处翻译:
关闭此流并释放与之相关的任何系统资源
它还有一个父亲 AutoCloseable, 我们来看看
我们看下翻译:
一个可以持有资源(如文件或套接字句柄)的对象,直到它被关闭。
AutoCloseable对象的close()方法在退出try-with-resources块时被自动调用,该对象已在资源规范头中声明。这种结构确保了及时释放,避免了可能发生的资源耗尽的异常和错误。
API说明。即使基类的所有子类或实例都持有可释放的资源,基类也有可能实现 AutoCloseable,而且事实上这种情况很常见。对于必须完全通用的代码,或者当已知AutoCloseable实例需要释放资源时,建议使用try-with-resources结构。然而,当使用诸如java.util.stream.Stream这样同时支持基于I/O和非基于I/O形式的设施时,一般来说,当使用非基于I/O形式时,try-with-resources块是不必要的
关闭此资源,放弃任何基础资源。这个方法在由try-with-resources语句管理的对象上被自动调用。
虽然这个接口方法被声明为抛出Exception,但我们强烈鼓励实现者声明close方法的具体实现,以抛出更具体的异常,或者如果关闭操作不会失败,则根本不抛出异常。
关闭操作可能失败的情况需要实现者的仔细关注。我们强烈建议在抛出异常之前,放弃基础资源,并在内部将资源标记为关闭。关闭方法不太可能被多次调用,因此这可以确保资源被及时释放。此外,它还减少了资源被其他资源包裹时可能出现的问题。
我们也强烈建议这个接口的实现者不要让close方法抛出InterruptedException。这个异常与线程的中断状态相互作用,如果抑制了InterruptedException,很可能会发生运行时的错误行为。更一般地说,如果抑制一个异常会导致问题,AutoCloseable.close方法就不应该抛出它。
注意,与java.io.Closeable的close方法不同,这个close方法不需要是empotent的。换句话说,多次调用这个close方法可能会产生一些可见的副作用,而不像Closeable.close被要求在多次调用时不产生影响。然而,我们强烈建议这个接口的实现者使他们的关闭方法具有可执行性。
抛出。
异常 - 如果该资源不能被关闭
意味着, 我们JDK7.0以后, 流资源的关闭, 都和AutoCloseable挂钩了
说得再通俗一点:
直接或间接实现AutoCloseable接口的类, 都能调用close()方法
好的, 那话不多说,
其中的函数用到了泛型, 意味传进来的参数是实现了AutoCloseable接口的
public class NetFunction {
public static void closeStream(T t) {
if (t != null) {
try {
t.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这样, 即使流放不进try里面, 也可以愉快的关闭啦.
什么?你还想再少一点...?
把所有的东西都放进去一起关闭?
比如说
NetFunction.closeStream(dos1,dos2,socket);
额, 那就再改改代码
public static void close(T... ts) {
for (T t : ts) {
if (t != null) {
try {
t.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
怎么样, 是不是满足了所有要求了?
可以批量关闭传进去的流对象
是的, 但是我看编译器的时候强迫症犯了...
百度了一下
额, 大概就是 使用泛型的时候用可变形参会导致这个问题发生, 加个注解即可
但是我强迫症, 加个注解显然不优雅, 又想消除这一警告
最后沉吟许久, 想到了Java基础知识里面的转型...
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);//一次性关闭好多的流 写的还很短,是不是很赞?
PS:这种最终加强方法适合于,不在try里面创建的流,也就是说需要手动关闭的,try里面自动关闭的不需要我们释放了。
如果能用第二种,就优先第二种
如果还有更好的办法, 欢迎在评论区指出