96

注意:这个问题及其大部分答案都可以追溯到 Java 7 发布之前。Java 7 提供了自动资源管理功能,可以轻松地做到这一点。如果您使用的是 Java 7 或更高版本,您应该提前了解 Ross Johnson 的答案


什么被认为是在 Java 中关闭嵌套流的最佳、最全面的方法?例如,考虑设置:

FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);

我知道关闭操作需要被保险(可能通过使用 finally 子句)。我想知道的是,是否有必要明确确保嵌套流已关闭,或者仅确保关闭外部流(oos)就足够了?

我注意到的一件事,至少在处理这个特定示例时,内部流似乎只抛出 FileNotFoundExceptions。这似乎意味着从技术上讲,如果它们失败,则无需担心关闭它们。

这是同事写的:


从技术上讲,如果实施得当,关闭最外层流 (oos) 就足够了。但实施似乎有缺陷。

示例:BufferedOutputStream 继承自 FilterOutputStream 的 close(),它定义为:

 155       public void close() throws IOException {
 156           try {
 157             flush();
 158           } catch (IOException ignored) {
 159           }
 160           out.close();
 161       }

但是,如果 flush() 出于某种原因抛出运行时异常,则永远不会调用 out.close()。因此,主要担心关闭 FOS 似乎是“最安全的”(但丑陋),这会使文件保持打开状态。


什么被认为是关闭嵌套流的最佳方法?

是否有任何官方的 Java/Sun 文档可以详细处理这个问题?

4

10 回答 10

41

关闭链式流时,只需要关闭最外层的流即可。任何错误都将向上传播并被捕获。

有关详细信息,请参阅Java I/O 流

解决问题

但是,如果 flush() 出于某种原因抛出运行时异常,则永远不会调用 out.close()。

这是不对的。捕获并忽略该异常后,将在 catch 块之后恢复执行,并out.close()执行该语句。

您的同事对运行时异常提出了很好的观点。如果您绝对需要关闭流,您可以随时尝试从外向内单独关闭每个流,在第一个异常处停止。

于 2009-05-19T17:17:45.603 回答
32

在 Java 7 时代,try-with-resources肯定是要走的路。如前几个答案所述,关闭请求从最外层流传播到最内层流。因此,只需一次关闭即可。

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
  // do something with ois
}

然而,这种模式存在一个问题。try-with-resources 不知道内部的 FileInputStream,因此如果 ObjectInputStream 构造函数抛出异常,则 FileInputStream 永远不会关闭(直到垃圾收集器到达它)。解决方案是...

try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
  // do something with ois
}

这不是那么优雅,但更健壮。这是否真的是一个问题将取决于在构建外部对象期间可以抛出哪些异常。ObjectInputStream 可以抛出 IOException ,这很可能会被应用程序处理而不会终止。许多流类只抛出未经检查的异常,这很可能导致应用程序终止。

于 2014-02-12T09:06:32.093 回答
22

使用 Apache Commons 处理 IO 相关对象是一种很好的做法。

finally子句中使用IOUtils

IOUtils.closeQuietly(bWriter); IOUtils.closeQuietly(oWritter);

下面的代码片段。

BufferedWriter bWriter = null;
OutputStreamWriter oWritter = null;

try {
  oWritter  = new OutputStreamWriter( httpConnection.getOutputStream(), "utf-8" );
  bWriter = new BufferedWriter( oWritter );
  bWriter.write( xml );
}
finally {
  IOUtils.closeQuietly(bWriter);
  IOUtils.closeQuietly(oWritter);
}
于 2011-03-16T16:06:26.007 回答
19

我通常会执行以下操作。首先,定义一个基于模板方法的类来处理 try/catch 混乱

import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public abstract class AutoFileCloser {
    // the core action code that the implementer wants to run
    protected abstract void doWork() throws Throwable;

    // track a list of closeable thingies to close when finished
    private List<Closeable> closeables_ = new LinkedList<Closeable>();

    // give the implementer a way to track things to close
    // assumes this is called in order for nested closeables,
    // inner-most to outer-most
    protected final <T extends Closeable> T autoClose(T closeable) {
            closeables_.add(0, closeable);
            return closeable;
    }

    public AutoFileCloser() {
        // a variable to track a "meaningful" exception, in case
        // a close() throws an exception
        Throwable pending = null;

        try {
            doWork(); // do the real work

        } catch (Throwable throwable) {
            pending = throwable;

        } finally {
            // close the watched streams
            for (Closeable closeable : closeables_) {
                if (closeable != null) {
                    try {
                        closeable.close();
                    } catch (Throwable throwable) {
                        if (pending == null) {
                            pending = throwable;
                        }
                    }
                }
            }

            // if we had a pending exception, rethrow it
            // this is necessary b/c the close can throw an
            // exception, which would remove the pending
            // status of any exception thrown in the try block
            if (pending != null) {
                if (pending instanceof RuntimeException) {
                    throw (RuntimeException) pending;
                } else {
                    throw new RuntimeException(pending);
                }
            }
        }
    }
}

请注意“待处理”异常——这会处理关闭期间抛出的异常会掩盖我们可能真正关心的异常的情况。

finally 会首先尝试从任何装饰流的外部关闭,因此如果您有一个包装 FileWriter 的 BufferedWriter,我们会尝试先关闭 BuffereredWriter,如果失败,仍然尝试关闭 FileWriter 本身。(注意 Closeable 的定义调用 close() 如果流已经关闭则忽略该调用)

您可以按如下方式使用上述类:

try {
    // ...

    new AutoFileCloser() {
        @Override protected void doWork() throws Throwable {
            // declare variables for the readers and "watch" them
            FileReader fileReader = 
                    autoClose(fileReader = new FileReader("somefile"));
            BufferedReader bufferedReader = 
                    autoClose(bufferedReader = new BufferedReader(fileReader));

            // ... do something with bufferedReader

            // if you need more than one reader or writer
            FileWriter fileWriter = 
                    autoClose(fileWriter = new FileWriter("someOtherFile"));
            BufferedWriter bufferedWriter = 
                    autoClose(bufferedWriter = new BufferedWriter(fileWriter));

            // ... do something with bufferedWriter
        }
    };

    // .. other logic, maybe more AutoFileClosers

} catch (RuntimeException e) {
    // report or log the exception
}

使用这种方法,您永远不必担心 try/catch/finally 会再次处理关闭文件。

如果这对您的使用来说太重了,至少考虑遵循 try/catch 和它使用的“待定”变量方法。

于 2009-05-19T17:53:50.583 回答
5

这位同事提出了一个有趣的观点,无论哪种方式都有理由争论。

就个人而言,我会忽略RuntimeException, 因为未经检查的异常表示程序中的错误。如果程序不正确,请修复它。您不能在运行时“处理”坏程序。

于 2009-05-19T17:40:21.053 回答
5

这是一个令人惊讶的尴尬问题。(即使假设acquire; try { use; } finally { release; }代码是正确的。)

如果装饰器的构造失败,那么您将不会关闭底层流。因此,您确实需要显式关闭底层流,无论是在最终使用后,还是在成功将资源移交给装饰器后更困难)。

如果异常导致执行失败,你真的要刷新吗?

一些装饰器实际上自己有资源。例如,当前的 Sun 实现ZipInputStream分配了非 Java 堆内存。

有人声称(IIRC)Java 库中三分之二的资源使用是以明显不正确的方式实现的。

虽然BufferedOutputStream即使在IOExceptionfromflush上也BufferedWriter关闭,但正确关闭。

我的建议:尽可能直接关闭资源,不要让它们污染其他代码。OTOH,您可能会在这个问题上花费太多时间 - 如果OutOfMemoryError抛出它,表现得很好,但您程序的其他方面可能具有更高的优先级,并且无论如何在这种情况下库代码可能会被破坏。但我总是写:

final FileOutputStream rawOut = new FileOutputStream(file);
try {
    OutputStream out = new BufferedOutputStream(rawOut);
    ... write stuff out ...
    out.flush();
} finally {
    rawOut.close();
}

(看:没有抓住!)

也许使用 Execute Around 成语。

于 2009-05-19T18:05:47.490 回答
1

似乎没有提到Java SE 7 try-with-resources 。它消除了完全明确地关闭的需要,我非常喜欢这个想法。

不幸的是,对于 Android 开发,只有使用 Android Studio(我认为)并针对 Kitkat 及更高版本才能获得这种甜蜜。

于 2014-02-07T13:50:35.007 回答
0

此外,您不必关闭所有嵌套流

检查这个 http://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html

于 2011-02-06T14:18:06.143 回答
0

我习惯像这样关闭流,而不在 finally块中嵌套 try-catch

public class StreamTest {

public static void main(String[] args) {

    FileOutputStream fos = null;
    BufferedOutputStream bos = null;
    ObjectOutputStream oos = null;

    try {
        fos = new FileOutputStream(new File("..."));
        bos = new BufferedOutputStream(fos);
        oos = new ObjectOutputStream(bos);
    }
    catch (Exception e) {
    }
    finally {
        Stream.close(oos,bos,fos);
    }
  }   
}

class Stream {

public static void close(AutoCloseable... array) {
    for (AutoCloseable c : array) {
        try {c.close();}
        catch (IOException e) {}
        catch (Exception e) {}
    }
  } 
}
于 2016-02-25T10:00:43.803 回答
-1

Sun 的 JavaDocsRuntimeException在其文档中包含 s,如 InputStream 的read(byte[], int, int)方法所示;记录为抛出 NullPointerException 和 IndexOutOfBoundsException。

FilterOutputStream 的flush()仅记录为抛出 IOException,因此它实际上并没有抛出任何RuntimeExceptions。任何可以抛出的东西很可能都被包裹在一个IIOException.

它仍然可以抛出一个Error,但是您对此无能为力;Sun 建议您不要试图抓住它们。

于 2009-05-19T18:39:48.493 回答