面试官:IO 操作必须要手动关闭吗?关闭流方法是不是有顺序?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官:IO 操作必须要手动关闭吗?关闭流方法是不是有顺序?相关的知识,希望对你有一定的参考价值。

参考技术A

平时我们使用输入流和输出流一般都会使用buffer包装一下,直接看下面代码(这个代码运行正常,不会报错)

下面我们来研究下这段代码的 bufferedOutputStream.close(); 方法是否调用了 fileOutputStream.close();

先看 BufferedOutputStream 源代码:

可以看到它继承 FilterOutputStream ,并且没有重写close方法,所以直接看 FilterOutputStream 的源代码:

跟踪out( FilterOutputStream 中):

再看看 BufferedOutputStream 中:

可以看到 BufferedOutputStream 调用 super(out); ,也就是说, out.close(); 调用的是通过 BufferedOutputStream 传入的被包装的流,这里就是 FileOutputStream 。

我们在看看其他类似的,比如 BufferedWriter 的源代码:

通过观察各种流的源代码,可得结论: 包装的流都会自动调用被包装的流的关闭方法,无需自己调用。

由上面的结论,就会产生一个问题: 如果手动关闭被包装流会怎么样,这个关闭流有顺序吗? 而实际上我们习惯都是两个流都关闭的。

首先我们来做一个简单的实验,基于第一个问题的代码上增加手动增加关闭流的代码,那么就有两种顺序:

上述两种写法都没有问题,我们已经知道 bufferedOutputStream.close(); 会自动调用 fileOutputStream.close(); 方法,那么这个方法是怎么执行的呢?我们又看看 FileOutputStream 的源码:

可以看出它采用同步锁,而且使用了关闭标记,如果已经关闭了则不会再次操作,所以多次调用不会出现问题。

如果没有看过参考文章,我可能就会断下结论,关闭流不需要考虑顺序

我们看下下面的代码(修改自参考文章):

会抛出 Stream closed 的IO异常:

而如果把 bw.close(); 放在第一,其他顺序任意,即修改成下面两种:

都不会报错,这是为什么呢,我们立即看看 BufferedWriter 的close源码:

里面调用了 flushBuffer() 方法,也是抛异常中的错误方法:

可以看到很大的一行

这行如果在流关闭后执行就会抛IO异常,有时候我们会写成:

这样也会抛异常,不过是由于 flushBuffer() 中 ensureOpen() 抛的,可从源码中看出:

如何防止这种情况?

直接写下面这种形式就可以:

结论:一个流上的close方法可以多次调用,理论上关闭流不需要考虑顺序,但有时候关闭方法中调用了write等方法时会抛异常。

由上述的两个结论可以得出下面的建议:

关闭流只需要关闭最外层的包装流,其他流会自动调用关闭,这样可以保证不会抛异常。 如:

注意的是,有些方法中close方法除了调用被包装流的close方法外还会把包装流置为null,方便JVM回收。 bw.close() 中的:

finally中就有把out置为null的代码,所以有时候不需要自己手动置为null。(个人建议还是写一下,不差多少执行时间)

golang 连接、操作完mysql, 对mysql的连接会自动关闭,还是必须要手动关闭?

求教:这段代码对数据库的连接没有主动关闭(调用 db.Close()), 是不是Go的垃圾收集自动关闭释放资源呢? 还是必须手动调用db.Close()释放资源?[mw_shl_code=applescript,true]import ( _ "code.google.com/p/go-mysql-driver/mysql" "database/sql" "fmt" //"time")func main() db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8") checkErr(err) //插入数据 stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") checkErr(err) id, err := res.LastInsertId() checkErr(err) fmt.Println(id) //删除数据 stmt, err = db.Prepare("delete from userinfo where uid=?") checkErr(err) res, err = stmt.Exec(id) checkErr(err) affect, err = res.RowsAffected() checkErr(err) fmt.Println(affect)func checkErr(err error) if err != nil panic(err) [/mw_shl_code]

Go垃圾回收是内存垃圾回收,分配给对象的内存回收。对于资源,必须手动释放,还给操作系统 参考技术A 不手动关的话,可能在你程序结束的时候,或者这个连接对象出了作用域的时候,golang会帮你自动关闭。。。不过还是支持手动关闭。。。这是好习惯。。。 参考技术B 当然可以不关闭,mysql过会都会自动关了的。但要养成手动关的习惯。

以上是关于面试官:IO 操作必须要手动关闭吗?关闭流方法是不是有顺序?的主要内容,如果未能解决你的问题,请参考以下文章

java中IO流操作怎样关闭流

golang 连接、操作完mysql, 对mysql的连接会自动关闭,还是必须要手动关闭?

java;怎么关闭流文件?

为啥JAVA IO流 局部的每次打开 循环结束后必须要关闭呢

Java中,static代码块创建的IO流需要手动关闭吗?

细节打满,IO 操作必须手动关闭?关闭流方法是否有顺序?