在Java中关闭嵌套流和编写器的正确方法

时间:2009-05-19 17:14:31

标签: java java-io

注意:这个问题及其大部分答案都发布在Java 7发布之前.Java 7提供了Automatic Resource Management功能,可以轻松实现这一目标。如果您使用的是Java 7或更高版本,则应该前进到the answer of 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文档可以详细解决这个问题?

10 个答案:

答案 0 :(得分:39)

关闭链接流时,只需关闭最外层的流。任何错误都会在链中传播并被捕获。

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

解决问题

  

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

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

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

答案 1 :(得分:25)

在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,它可以很好地由应用程序处理而不会终止。许多流类只会抛出未经检查的异常,这可能会导致应用程序终止。

答案 2 :(得分:21)

使用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);
}

答案 3 :(得分:18)

我通常会做以下事情。首先,定义一个基于模板方法的类来处理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);
                }
            }
        }
    }
}

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

最后尝试从任何装饰流的外部关闭,所以如果你有一个包装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和它使用的“待定”变量方法。

答案 4 :(得分:5)

这位同事提出了一个有趣的观点,并且有理由以任何一种方式争论。

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

答案 5 :(得分:5)

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

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

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

一些装饰者实际上拥有资源。例如,ZipInputStream的当前Sun实现具有分配的非Java堆内存。

据称,(IIRC)Java库中三分之二的资源使用方式明显不正确。

即使在BufferedOutputStream IOException flush关闭BufferedWriterOutOfMemoryError也会正常关闭。

我的建议:尽可能直接关闭资源,不要让他们污染其他代码。 OTOH,你可以在这个问题上花费太多时间 - 如果抛出final FileOutputStream rawOut = new FileOutputStream(file); try { OutputStream out = new BufferedOutputStream(rawOut); ... write stuff out ... out.flush(); } finally { rawOut.close(); } ,那么表现得很好很好,但程序的其他方面可能是一个更高的优先级,而且在这种情况下库代码可能会被打破。但我总是写:

{{1}}

(看:没问题!)

也许使用Execute Around成语。

答案 6 :(得分:1)

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

不幸的是,对于Android开发,只有使用Android Studio(我认为)和targeting Kitkat and above才能获得这种甜蜜。

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

我用来关闭像这样的流,没有在最终块中嵌套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) {}
    }
  } 
}

答案 9 :(得分:-1)

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

FilterOutputStream的flush()仅记录为抛出IOException,因此它实际上不会抛出任何RuntimeException。任何可能被抛出的东西都很可能被包裹在IIOException中。

它仍然可以抛出一个Error,但是你可以做的不多; Sun建议您不要试图抓住它们。