在Java中关闭输入流

28

我在try/catch块中有以下代码段

 InputStream inputstream = conn.getInputStream();
 InputStreamReader inputstreamreader = new  InputStreamReader(inputstream);
 BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

我的问题是,当我必须在finally块中关闭这些流时,我是否需要关闭所有3个流,还是只关闭 bufferedreader 将会关闭所有其他流?


6个回答

31

按照惯例,包装流(包装现有流)在关闭时会关闭底层流,因此在您的示例中只需关闭bufferedreader。此外,关闭已经关闭的流通常是无害的,因此关闭所有3个流不会有任何影响。


你不能保证流实现在关闭时不会抛出异常,如果它已经关闭。因此,最好坚持正确的方式,只关闭 BufferedReader。 - Soronthar
2
@Soronthar,那么这个流实现不符合合同规定,合同规定“如果流已经关闭,则调用此方法无效。”所有有效的实现都必须遵守此要求。关闭已经关闭的流是完全合法的(并且常见的)。通常你在try块中关闭它的第一次,在finally块中关闭它的第二次,其中在try块中关闭给你机会在异常上做出反应,在finally块中关闭是为了确保关闭。 - Fabian Barney
@FabianBarney 你说得对,我忘记了他们在JSE 6中使用Closeable接口对流进行了改进。 - Soronthar
2
这不仅是惯例,而是规定。例如,参见FilerInputStream.close()。 - user207421

8
通常情况下,只关闭最外层流就可以了,因为按照惯例它必须触发底层流的关闭操作。
所以通常的代码看起来像这样:
BufferedReader in = null;

try {
    in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    ...
    in.close(); // when you care about Exception-Handling in case when closing fails
}
finally {
    IOUtils.closeQuietly(in); // ensure closing; Apache Commons IO
}

然而,在一些罕见的情况下,当流已经打开时,底层流构造函数可能会引发异常。在这种情况下,上述代码将无法关闭底层流,因为外部构造函数从未被调用,in为空。所以finally块不会关闭任何东西,使底层流保持打开状态。

自Java 7以来,您可以这样做:

    try (OutputStream out1 = new ...; OutputStream out2 = new ...) {
        ...
        out1.close(); //if you want Exceptions-Handling; otherwise skip this
        out2.close(); //if you want Exceptions-Handling; otherwise skip this            
    } // out1 and out2 are auto-closed when leaving this block

在大多数情况下,当关闭时引发异常时,您不需要进行异常处理,因此可以跳过这些显式的close()调用。 编辑 以下是一些代码,供那些必须使用此模式的非信徒参考。您可能还想阅读Apache Commons IOUtils javadoc有关closeQuietly()方法的说明。
    OutputStream out1 = null;
    OutputStream out2 = null;

    try {
        out1 = new ...;
        out2 = new ...;

        ...

        out1.close(); // can be skipped if we do not care about exception-handling while closing
        out2.close(); // can be skipped if we ...
    }
    finally {
        /*
         * I've some custom methods in my projects overloading these
         * closeQuietly() methods with a 2nd param taking a logger instance, 
         * because usually I do not want to react on Exceptions during close 
         * but want to see it in the logs when it happened.
         */
        IOUtils.closeQuietly(out1);
        IOUtils.closeQuietly(out2);
    }

使用@Tom的“建议”会导致在创建out2时引发异常时留下out1打开。这个建议来自某个人谈论它是一个明显错误的持续来源。嗯,我可能很瞎,但对我来说这不是显而易见的。我的模式在我能想到的每种用例中都是傻瓜安全的,而Tom的模式容易出错。

请不要做那种null舞蹈。由于明显的原因,这将是错误的持续来源。而且会造成很大混乱。 - Tom Hawtin - tackline
1
这是正常的模式。您还想做什么? - Fabian Barney
acquire(); try { use(); } finally { release(); } - Tom Hawtin - tackline
@Bruno Right - 这就是我在最后一段所说的意思。 - Fabian Barney
@TomHawtin-tackline,所以你在获取时不会释放异常?这很糟糕,特别是在使用特殊的OutputStreams(如TeeOutputStream)时,其中一个已经创建而另一个失败。 - Fabian Barney
显示剩余8条评论

3
只需要关闭最外层的(即BufferedReader),这就足够了。阅读BufferedReader的源代码,我们可以看到当调用它自己的close方法时,它会关闭内部的Reader
513       public void close() throws IOException {
514           synchronized (lock) {
515               if (in == null)
516                   return;
517               in.close();
518               in = null;
519               cb = null;
520           }
521       }
522   }

0
通常情况下,您应该按照相反的顺序关闭您打开的所有内容。

0

我会按照与打开相反的顺序关闭它们,好像在打开它们时将读者推到堆栈中,而关闭则从堆栈中弹出读者。

最后,在全部关闭之后,“读者堆栈”必须为空。


你为什么要这样推荐?当过滤输入流和读取器被指定为在close()时关闭嵌套流时? - user207421
只是常识,我想。正如投票所示,这个线程中有更好的答案。一些人甚至展示了库的源代码。 - João Rocha da Silva

0

您只需要关闭实际资源。即使构造装饰器失败,也应该关闭资源。对于输出,您应该在正常情况下刷新最后一个装饰器对象。

一些复杂情况:

  • 有时装饰器是不同的资源(某些压缩实现使用 C 堆)。
  • 在悲伤的情况下关闭装饰器实际上会导致刷新,从而导致混淆,例如实际上没有关闭底层资源。
  • 看起来您的底层资源是 URLConnection,它没有像这样的 disconnect/close 方法。

您可能希望考虑使用“执行周围”惯用语,这样您就不必重复这种事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接