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

dir*_*ond 92 java java-io

注意:这个问题及其大多数答案都发布在Java 7发布之前.Java 7提供了自动资源管理功能,可以轻松实现这一目标.如果您使用的是Java 7或更高版本,那么您应该转到Ross Johnson的答案.


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

FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);
Run Code Online (Sandbox Code Playgroud)

我理解需要保证关闭操作(可能使用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       }
Run Code Online (Sandbox Code Playgroud)

但是,如果flush()由于某种原因抛出运行时异常,则永远不会调用out.close().所以看起来"最安全"(但很丑陋)主要担心关闭FOS,即保持文件打开.


什么被认为是最好的,当你绝对需要确定,关闭嵌套流的方法?

是否有任何正式的Java/Sun文档可以详细解决这个问题?

Bil*_*ard 39

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

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

解决这个问题

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

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

你的同事对Runtime Exception 做了一个很好的观点.如果您绝对需要关闭流,您可以尝试从外部单独关闭每个流,在第一个例外停止.

  • 我不会停下来,我会继续关闭并记录异常.BufferedStream的案例令人痛心.如果BufferedStream在关闭期间内爆,你(可能)不希望FileOutputStream在"unclosed"周围徘徊. (2认同)

小智 27

在Java 7时代,尝试使用资源肯定是要走的路.如前几个答案中所述,关闭请求从最外层流传播到最内层流.所以只需要一次关闭即可.

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
  // do something with ois
}
Run Code Online (Sandbox Code Playgroud)

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

try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
  // do something with ois
}
Run Code Online (Sandbox Code Playgroud)

这不是那么优雅,但更强大.这是否实际上是一个问题将取决于在构造外部对象期间可以抛出什么异常.ObjectInputStream可以抛出IOException,它可以很好地由应用程序处理而不会终止.许多流类只会抛出未经检查的异常,这很可能导致应用程序终止.


小智 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);
}
Run Code Online (Sandbox Code Playgroud)


Sco*_*eld 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);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意"挂起"异常 - 这将处理在关闭期间抛出的异常将掩盖我们可能真正关心的异常的情况.

最后尝试从任何装饰流的外部关闭,所以如果你有一个包装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
}
Run Code Online (Sandbox Code Playgroud)

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

如果这对你的使用太重,至少要考虑遵循try/catch和它使用的"待定"变量方法.

  • 你能不能写出例如`FileReader fileReader = autoClose(new FileReader("somefile"));`并且具有相同的效果? (4认同)

eri*_*son 5

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

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

  • 我不会指望RuntimeException意味着这样的错误(错误意味着这一点).有些开发人员认为未经检查的异常对所有用例都有好处,而其他人则非常不同意. (3认同)
  • 因为我正在连接它们,所以我知道装饰器正在使用中.在一个特殊情况下,我被迫使用不正确使用RuntimeExceptions的流,我必须考虑到这一点.尽管如此,还没有面对过.编写大量不必要的复杂代码来防范不可能和可避免的事情似乎是一种糟糕的方法. (3认同)

Tom*_*ine 5

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

如果装饰器的构造失败,那么您将不会关闭底层流.因此,您需要显式关闭基础流,无论是在使用后的最终版本中,还是在成功将资源移交给装饰器之后的更多diifcult).

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

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

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

BufferedOutputStream即使是在一个封闭IOExceptionflush,BufferedWriter正确关闭.

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

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

(看:没有抓到!)

也许使用Execute Around成语.