黑马程序员技术交流社区

标题: IO流之澄清JDK API文档中一句带有误导性的话 [打印本页]

作者: fantacyleo    时间: 2014-7-18 11:55
标题: IO流之澄清JDK API文档中一句带有误导性的话
昨天看老毕的基础视频看到BufferedWriter。对于BufferedWriter的close()方法,老毕讲得很清楚,关闭的是BufferedWriter所包装的FileWriter流,因此调用该close后,无需再调用FileWriter的close()方法。我比较喜欢追究细节,于是打开BufferedWriter的close()源码看了一下,是这样写的
  1. public void close() throws IOException {
  2.     synchronized (lock) {
  3.         if (out == null) {
  4.             return;
  5.         }
  6.         try {
  7.             flushBuffer();
  8.         } finally {
  9.             out.close();
  10.             out = null;
  11.             cb = null;
  12.         }
  13.     }
  14. }
复制代码


其中的out,就是指向你创建BufferedWriter对象时往构造函数里传的那个FileWriter。因此,BufferedWriter的close()方法确实关闭了底层的FileWriter流,无需再来一次close()。可是如果大家到网上一搜,就会发现还是有不少文章说BufferedWriter和它所包装的FileWriter的close()方法都要调用一次,而且必须先调BufferedWriter的close,再调FileWriter的close,否则会报异常,说流已关闭。我读到这里觉得疑惑,JDK API文档在BufferedWriter 和FileWriter 的close()方法中明明都写了一句:Closing a previously closed stream has no effect(关闭一个之前已被关闭的流,不会产生任何效果)啊,怎么先关BufferedWriter再关FileWriter就报异常了呢?

又把BufferedWriter的close()方法源码看了几遍,推测是那句flushBuffer()出了问题:假设做了如下声明:
  1. FileWriter fw = new FileWriter("demo.txt");
  2. BufferedWriter bw = new BufferedWriter(fw);
复制代码


如果调用fw.close()关闭流却没有把fw设为null,那么紧接着调用bw.close()时,if(out==null)判定就为假,将执行flushBuffer(),而这个flushBuffer()的源码是:
  1. public void flush() throws IOException {
  2.     synchronized (lock) {
  3.         flushBuffer();
  4.         out.flush();
  5.     }
  6. }
复制代码

它调用out(也就是fw)的flush()方法,然而此时流已经关闭,当然会报异常!

那么为何JDK API文档又说“Closing a previously closed stream has no effect”呢?呵呵,其实人家是说:如果你多次调用fw.close()【或】多次调用bw.close()不会出问题,而不是说你混合着调用两者不会出问题。对bw.close()来说,当你调用了一次后,它底层的Writer流就被设为null(out=null),下次再调用时被if(out==null)拦下,直接return,确实"has no effect"。另一方面,fw.close()的源码实际上是调用了一个叫StreamEncoding类的close()方法。这个类挺神秘,下载的JDK源码包里没它,得去http://www.docjar.com/html/api/sun/nio/cs/StreamEncoder.java.html 查看。它的close()方法代码如下:
  1. public void close() throws IOException {
  2.        synchronized (lock) {
  3.            if (!isOpen)
  4.                return;
  5.            implClose();
  6.            isOpen = false;
  7.        }
  8.    }
复制代码

哦,原来人家内部设置了一个boolean变量isOpen,关闭流的时候把这个变量设为false,下次再close()就直接return了。所以你fw.close()执行再多次也不会出问题。

总结一下:JDK API文档在BufferedWriter 和FileWriter 的close()方法中都写有一句:Closing a previously closed stream has no effect(关闭一个之前已被关闭的流,不会产生任何效果)。这句话只是告诉你,你可以多次调用同一个对象的close()方法,不会出问题。但是由于JDK源码实现的特点,如果你先调FileWriter的close再调BufferedWriter的close,就会报流关闭异常。必须先关闭BufferedWriter再关闭FileWriter。当然,从方便角度和逻辑层面来说,最佳的选择正是老毕基础视频里所做的那样,只调用BufferedWriter的close就可以了,省得给自己找麻烦。
作者: 青偆丶易逝〃    时间: 2014-7-18 12:14
哇,喜欢你这种研究精神。。 强大
作者: 戒风    时间: 2014-7-18 12:16
哇厉害,
作者: shen7518    时间: 2014-7-18 13:17
受教。。
作者: 赵顺超    时间: 2014-7-18 13:44
学习。。
作者: SLJ_920808    时间: 2014-7-18 13:46
赞一个!
作者: 黄宝宝    时间: 2014-7-18 13:53
研究精神赞一下!
作者: 乐此不疲    时间: 2014-7-18 14:20
学习了~
作者: 慕杰    时间: 2014-7-21 00:06
  1. void flushBuffer() throws IOException {
  2.         synchronized (lock) {
  3.             ensureOpen();
  4.             if (nextChar == 0)
  5.                 return;
  6.             out.write(cb, 0, nextChar);
  7.             nextChar = 0;
  8.         }
  9.     }
复制代码


我想说我看到的flushBuffer()源码是这个样子的,ensureOpen的作用就是判断writer是否关闭,ensureOpen()的源码如下:
  1. /** Checks to make sure that the stream has not been closed */
  2.     private void ensureOpen() throws IOException {
  3.         if (out == null)
  4.             throw new IOException("Stream closed");
  5.     }
复制代码

所以这里throw了异常需要自己去处理吧?难道我JDK1.7的原因???我就是看了一下源码,没仔细看。
作者: 黑马-蒋振军    时间: 2014-7-21 10:35
感谢楼主分享
作者: 高老强    时间: 2014-7-21 15:54
假以时日,你将会变得很强大
作者: a6511631    时间: 2014-7-22 08:34
个人理解不一样,我看到那句话,就没有像你这样想着混合来关。
作者: 曲佳奇    时间: 2014-7-22 10:17
学习。。。
作者: 为爱编程    时间: 2014-8-1 13:06
版主都去哪了,明显这是加技术分的节奏




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2