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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/884007/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:39:25  来源:igfitidea点击:

Correct way to close nested streams and writers in Java

javajava-io

提问by dirtyvagabond

Note:This question and most of its answers date to before the release of Java 7. Java 7 provides Automatic Resource Managementfunctionality for doing this easilly. If you are using Java 7 or later you should advance to the answer of Ross Johnson.

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



What is considered the best, most comprehensive way to close nested streams in Java? For example, consider the setup:

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

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

I understand the close operation needs to be insured (probably by using a finally clause). What I wonder about is, is it necessary to explicitly make sure the nested streams are closed, or is it enough to just make sure to close the outer stream (oos)?

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

One thing I notice, at least dealing with this specific example, is that the inner streams only seem to throw FileNotFoundExceptions. Which would seem to imply that there's not technically a need to worry about closing them if they fail.

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

Here's what a colleague wrote:

这是一位同事写的:



Technically, if it were implemented right, closing the outermost stream (oos) should be enough. But the implementation seems flawed.

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

Example: BufferedOutputStream inherits close() from FilterOutputStream, which defines it as:

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

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

However, if flush() throws a runtime exception for some reason, then out.close() will never be called. So it seems "safest" (but ugly) to mostly worry about closing FOS, which is keeping the file open.

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



What is considered to be the hands-down best, when-you-absolutely-need-to-be-sure, approach to closing nested streams?

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

And are there any official Java/Sun docs that deal with this in fine detail?

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

采纳答案by Scott Stanchfield

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

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

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)

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

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.

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

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

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

回答by Bill the Lizard

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 Streamsfor details.

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

To address the issue

为了解决这个问题

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

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

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.

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

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.

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

回答by erickson

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.

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

回答by Tom Hawtin - tackline

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

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

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).

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

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

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

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

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

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

有人声称(IIRC)Java 库中三分之二的资源使用显然是错误的。

Whilst BufferedOutputStreamcloses even on an IOExceptionfrom flush, BufferedWritercloses correctly.

虽然BufferedOutputStream即使在一个闭合IOExceptionflushBufferedWriter正确关闭。

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 OutOfMemoryErroris 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:

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

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.

也许使用 Execute Around 成语。

回答by Powerlord

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

Sun 的 JavaDocsRuntimeException在其文档中包含s,如 InputStream 的read(byte[], int, int)方法所示;记录为抛出 NullPointerException 和 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.

FilterOutputStream 的flush()仅记录为抛出 IOException,因此它实际上并没有抛出任何RuntimeExceptions。任何可以抛出的东西很可能会被包裹在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.

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

回答by Karthik Chandraraj

Also you dont have to close all nested streams

您也不必关闭所有嵌套流

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

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

回答by Rulix Batistil

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

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

In the finallyclause use IOUtils

finally子句中使用IOUtils

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

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

回答by akauppi

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

Java SE 7 try-with-resources似乎没有被提及。它完全不需要明确地完成关闭,我非常喜欢这个想法。

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

不幸的是,对于 Android 开发,这种甜蜜只能通过使用 Android Studio(我认为)并针对 Kitkat 及更高版本来实现

回答by Ross Johnson

In the Java 7 era, try-with-resourcesis 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.

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

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-with-resources 不知道内部 FileInputStream,因此如果 ObjectInputStream 构造函数抛出异常,则 FileInputStream 永远不会关闭(直到垃圾收集器到达它)。解决办法是...

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.

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

回答by Noor Nawaz

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

我用来关闭这样的流,而不在 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) {}
    }
  } 
}