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

注意: 这个问题及其大部分答案可以追溯到 Java7发布之前。Java7为此提供了 自动资源管理功能。如果您正在使用 Java7或更高版本,则应该升级到 罗斯 · 约翰逊的回答


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

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

我明白关闭操作需要投保(可能通过使用 finally 条款)。我想知道的是,是否有必要显式地确保嵌套流已关闭,还是仅仅确保关闭外部流(oos)就足够了?

我注意到,至少在处理这个特定示例时,内部流似乎只抛出 FileNotFoundException。这似乎意味着在技术上没有必要担心如果他们失败了就关闭它们。

下面是一位同事写的:


从技术上讲,如果实施正确,关闭最外层 流(oos)应该足够了,但实现似乎有缺陷。

例如: BufferedOutputStream 从 FilterOutputStream 继承 close () ,它将其定义为:

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

但是,如果 flash ()由于某种原因引发运行时异常,那么 Close ()将永远不会被调用 主要是担心关闭 FOS,这会使文件保持打开状态。


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

有没有官方的 Java/Sun 文档详细处理这个问题?

47026 次浏览

When closing chained streams, you only need to close the outermost stream. Any errors will be propagated up the chain and be caught.

Refer to Java I/O Streams for details.

To address the issue

However, if flush() throws a runtime exception for some reason, then out.close() will never be called.

This isn't right. After you catch and ignore that exception, execution will pick back up after the catch block and the out.close() statement will be executed.

Your colleague makes a good point about the RuntimeException. If you absolutely need the stream to be closed, you can always try to close each one individually, from the outside in, stopping at the first exception.

The colleague raises an interesting point, and there are grounds for arguing either way.

Personally, I would ignore the RuntimeException, because an unchecked exception signifies a bug in the program. If the program is incorrect, fix it. You can't "handle" a bad program at runtime.

I usually do the following. First, define a template-method based class to deal with the try/catch mess

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

Note the "pending" exception -- this takes care of the case where an exception thrown during close would mask an exception we might really care about.

The finally tries to close from the outside of any decorated stream first, so if you had a BufferedWriter wrapping a FileWriter, we try to close the BuffereredWriter first, and if that fails, still try to close the FileWriter itself. (Note that the definition of Closeable calls for close() to ignore the call if the stream is already closed)

You can use the above class as follows:

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
}

Using this approach you never have to worry about the try/catch/finally to deal with closing files again.

If this is too heavy for your use, at least think about following the try/catch and the "pending" variable approach it uses.

This is a surprisingly awkward question. (Even assuming the acquire; try { use; } finally { release; } code is correct.)

If the construction of the decorator fails, then you wont be closing the underlying stream. Therefore you do need to close the underlying stream explicitly, whether in the finally after use or, more diifcult after successfully handing over the resource to the decorator).

If an exception causes execution to fail, do you really want to flush?

Some decorators actually have resources themselves. The current Sun implementation of ZipInputStream for instance has non-Java heap memory allocated.

It has been claimed that (IIRC) two thirds of the resources uses in the Java library are implemented in a clearly incorrect manner.

Whilst BufferedOutputStream closes even on an IOException from flush, BufferedWriter closes correctly.

My advice: Close resources as directly as possible and don't let them taint other code. OTOH, you can spend too much time on this issue - if OutOfMemoryError is thrown it's nice to behave nicely, but other aspects of your program are probably a higher priority and library code is probably broken in this situation anyway. But I'd always write:

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

(Look: No catch!)

And perhaps use the Execute Around idiom.

Sun's JavaDocs include RuntimeExceptions in their documentation, as shown by InputStream's read(byte[], int, int) method; documented as throwing NullPointerException and IndexOutOfBoundsException.

FilterOutputStream's flush() is only documented as throwing IOException, thus it doesn't actually throw any RuntimeExceptions. Any that could be thrown would most likely be wrapped in an IIOException.

It could still throw an Error, but there's not much you can do about those; Sun recommends that you don't try to catch them.

Also you dont have to close all nested streams

check this http://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html

It is a good practice to use Apache Commons to handle IO related objects.

In the finally clause use IOUtils

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

Code snippet below.

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

The Java SE 7 try-with-resources doesn't seem to be mentioned. It eliminates needing to explicitly do a close completely, and I quite like the idea.

Unfortunately, for Android development this sweet only becomes available by using Android Studio (I think) and targeting Kitkat and above.

In the Java 7 era, try-with-resources is certainly the way to go. As mentioned in several previous answers, the close request propagates from the outermost stream to the innermost stream. So a single close is all that is required.

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

There is however a problem with this pattern. The try-with-resources is not aware of the inner FileInputStream, so if the ObjectInputStream constructor throws an exception, the FileInputStream is never closed (until the garbage collector gets to it). The solution is...

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

This is not as elegant, but is more robust. Whether this is actually a problem will depend on what exceptions can be thrown during construction of the outer object(s). ObjectInputStream can throw IOException which may well get handled by an application without terminating. Many stream classes only throw unchecked exceptions, which may well result in termination of the application.

I use to close streams like this, without nesting try-catch in finally blocks

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) {}
}
}
}