java 是否有必要分别关闭每个嵌套的 OutputStream 和 Writer?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28276423/
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-11-02 13:19:22  来源:igfitidea点击:

Is it necessary to close each nested OutputStream and Writer separately?

javafile-iooutputstreamwriter

提问by Adon Smith

I am writing a piece of code:

我正在写一段代码:

OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(gzipOutputStream));

Do I need to close every stream or writer like the following?

我是否需要像下面这样关闭每个流或编写器?

gzipOutputStream.close();
bw.close();
outputStream.close();

Or will just closing the last stream be fine?

还是关闭最后一个流就可以了?

bw.close();

回答by T.J. Crowder

Assuming all the streams get created okay, yes, just closing bwis fine with those stream implementations; but that's a big assumption.

假设所有的流获得创建好了,是的,刚刚闭幕bw的精细与流实现; 但这是一个很大的假设。

I'd use try-with-resources(tutorial) so that any issues constructing the subsequent streams that throw exceptions don't leave the previous streams hanging, and so you don't have to rely on the stream implementation having the call to close the underlying stream:

我将使用try-with-resources教程),以便构建引发异常的后续流的任何问题都不会使先前的流挂起,因此您不必依赖具有关闭调用的流实现底层流:

try (
    OutputStream outputStream = new FileOutputStream(createdFile);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
}

Note you no longer call closeat all.

请注意,您根本不再打电话close了。

Important note: To have try-with-resources close them, you mustassign the streams to variables as you open them, you cannot use nesting. If you use nesting, an exception during construction of one of the later streams (say, GZIPOutputStream) will leave any stream constructed by the nested calls inside it open. From JLS §14.20.3:

重要说明:要让 try-with-resources 关闭它们,您必须在打开它们时将流分配给变量,不能使用嵌套。如果您使用嵌套,则在构造较晚的流之一(例如,GZIPOutputStream)期间的异常将使由其内部的嵌套调用构造的任何流保持打开状态。从JLS §14.20.3

A try-with-resources statement is parameterized with variables(known as resources) that are initialized before execution of the tryblock and closed automatically, in the reverse order from which they were initialized, after execution of the tryblock.

try-with-resources 语句使用变量(称为资源)进行参数化,这些变量try块执行之前初始化并在块执行之后按照与它们初始化的相反顺序自动关闭try

Note the word "variables" (my emphasis).

注意“变量”这个词(我的重点)

E.g., don't do this:

例如,不要这样做:

// DON'T DO THIS
try (BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(
        new GZIPOutputStream(
        new FileOutputStream(createdFile))))) {
    // ...
}

...because an exception from the GZIPOutputStream(OutputStream)constructor (which says it may throw IOException, and writes a header to the underlying stream) would leave the FileOutputStreamopen. Since some resources have constructors that may throw and others don't, it's a good habit to just list them separately.

...因为来自GZIPOutputStream(OutputStream)构造函数的异常(它说它可能 throw IOException,并将标头写入底层流)将保持FileOutputStream打开状态。由于某些资源具有可能抛出的构造函数而其他资源没有,因此将它们分开列出是一个好习惯。

We can double-check our interpretation of that JLS section with this program:

我们可以用这个程序仔细检查我们对 JLS 部分的解释:

public class Example {

    private static class InnerMost implements AutoCloseable {
        public InnerMost() throws Exception {
            System.out.println("Constructing " + this.getClass().getName());
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
        }
    }

    private static class Middle implements AutoCloseable {
        private AutoCloseable c;

        public Middle(AutoCloseable c) {
            System.out.println("Constructing " + this.getClass().getName());
            this.c = c;
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
            c.close();
        }
    }

    private static class OuterMost implements AutoCloseable {
        private AutoCloseable c;

        public OuterMost(AutoCloseable c) throws Exception {
            System.out.println("Constructing " + this.getClass().getName());
            throw new Exception(this.getClass().getName() + " failed");
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
            c.close();
        }
    }

    public static final void main(String[] args) {
        // DON'T DO THIS
        try (OuterMost om = new OuterMost(
                new Middle(
                    new InnerMost()
                    )
                )
            ) {
            System.out.println("In try block");
        }
        catch (Exception e) {
            System.out.println("In catch block");
        }
        finally {
            System.out.println("In finally block");
        }
        System.out.println("At end of main");
    }
}

...which has the output:

...输出:

Constructing Example$InnerMost
Constructing Example$Middle
Constructing Example$OuterMost
In catch block
In finally block
At end of main

Note that there are no calls to closethere.

请注意,那里没有调用close

If we fix main:

如果我们修复main

public static final void main(String[] args) {
    try (
        InnerMost im = new InnerMost();
        Middle m = new Middle(im);
        OuterMost om = new OuterMost(m)
        ) {
        System.out.println("In try block");
    }
    catch (Exception e) {
        System.out.println("In catch block");
    }
    finally {
        System.out.println("In finally block");
    }
    System.out.println("At end of main");
}

then we get the appropriate closecalls:

然后我们得到适当的close调用:

Constructing Example$InnerMost
Constructing Example$Middle
Constructing Example$OuterMost
Example$Middle closed
Example$InnerMost closed
Example$InnerMost closed
In catch block
In finally block
At end of main

(Yes, two calls to InnerMost#closeis correct; one is from Middle, the other from try-with-resources.)

(是的,两次调用InnerMost#close是正确的;一个来自Middle,另一个来自 try-with-resources。)

回答by Peter Lawrey

You can close the outer most stream, in fact you don't need to retain all the streams wrapped and you can use Java 7 try-with-resources.

您可以关闭最外面的流,实际上您不需要保留所有包裹的流,您可以使用 Java 7 try-with-resources。

try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                     new GZIPOutputStream(new FileOutputStream(createdFile)))) {
     // write to the buffered writer
}


If you subscribe to YAGNI, or you-aint-gonna-need-it, you should be only adding code you actually need. You shouldn't be adding code you imagine you might need but in reality doesn't do anything useful.

如果您订阅了 YAGNI,或者您不需要它,那么您应该只添加您实际需要的代码。您不应该添加您想象中可能需要的代码,但实际上并没有做任何有用的事情。

Take this example and imagine what could possibly go wrong if you didn't do this and what the impact would be?

以这个例子为例,想象一下如果你不这样做可能会出现什么问题以及会产生什么影响?

try (
    OutputStream outputStream = new FileOutputStream(createdFile);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
}

Lets start with FileOutputStream which calls opento do all the real work.

让我们从 FileOutputStream 开始,它调用open来完成所有实际工作。

/**
 * Opens a file, with the specified name, for overwriting or appending.
 * @param name name of file to be opened
 * @param append whether the file is to be opened in append mode
 */
private native void open(String name, boolean append)
    throws FileNotFoundException;

If the file is not found, there is no underlying resource to close, so closing it won't make any difference. If The file exists, it should be throwing a FileNotFoundException. So there is nothing to be gained by trying to close the resource from this line alone.

如果未找到该文件,则没有要关闭的基础资源,因此关闭它不会有任何区别。如果文件存在,它应该抛出一个 FileNotFoundException。因此,尝试仅从该行关闭资源没有任何好处。

The reason you need to close the file is when the file is opened successfully, but you later get an error.

您需要关闭文件的原因是文件成功打开时,但后来出现错误。

Lets look at the next stream GZIPOutputStream

让我们看看下一个流 GZIPOutputStream

There is code which can throw an exception

有可以抛出异常的代码

private void writeHeader() throws IOException {
    out.write(new byte[] {
                  (byte) GZIP_MAGIC,        // Magic number (short)
                  (byte)(GZIP_MAGIC >> 8),  // Magic number (short)
                  Deflater.DEFLATED,        // Compression method (CM)
                  0,                        // Flags (FLG)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Extra flags (XFLG)
                  0                         // Operating system (OS)
              });
}

This writes the header of the file. Now it would be very unusual for you to be able to open a file for writing but not be able to write even 8 bytes to it, but lets imagine this could happen and we don't close the file afterwards. What does happen to a file if it is not closed?

这将写入文件的标题。现在,您能够打开一个文件进行写入但甚至无法向其中写入 8 个字节,这将是非常不寻常的,但是让我们想象一下这可能发生并且我们之后不会关闭该文件。如果文件没有关闭会怎样?

You don't get any unflushed writes, they are discarded and in this case, there is no successfully written bytes to the stream which isn't buffered at this point anyway. But a file which is not closed doesn't live forever, instead FileOutputStream has

你不会得到任何未刷新的写入,它们被丢弃,在这种情况下,没有成功写入流的字节,无论如何此时都没有缓冲。但是没有关闭的文件不会永远存在,而是 FileOutputStream

protected void finalize() throws IOException {
    if (fd != null) {
        if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
            flush();
        } else {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }
}

If you don't close a file at all, it gets closed anyway, just not immediately (and like I said, data which is left in a buffer will be lost this way, but there is none at this point)

如果您根本不关闭文件,它无论如何都会关闭,只是不会立即关闭(就像我说的,留在缓冲区中的数据会以这种方式丢失,但此时没有)

What is the consequence of not closing the file immediately? Under normal conditions, you potentially lose some data, and you potentially run out of file descriptors. But if you have a system where you can create files but you can't write anything to them, you have a bigger problem. i.e. it hard to imagine why you are repeatedly trying to create this file despite the fact you are failing.

不立即关闭文件的后果是什么?在正常情况下,您可能会丢失一些数据,并且可能会用完文件描述符。但是,如果您有一个可以创建文件但不能向其中写入任何内容的系统,那么问题就更大了。即很难想象为什么尽管您失败了,但您仍反复尝试创建此文件。

Both OutputStreamWriter and BufferedWriter don't throw IOException in their constructors, so it not clear what problem they would cause. In The case of BufferedWriter, you could get an OutOfMemoryError. In this case it will immediately trigger a GC, which as we have seen will close the file anyway.

OutputStreamWriter 和 BufferedWriter 都不会在它们的构造函数中抛出 IOException,因此不清楚它们会导致什么问题。在 BufferedWriter 的情况下,您可能会收到 OutOfMemoryError。在这种情况下,它将立即触发 GC,正如我们所见,它无论如何都会关闭文件。

回答by Grzegorz ?ur

If all of the streams have been instantiated then closing only the outermost is just fine.

如果所有的流都被实例化了,那么只关闭最外面的流就可以了。

The documentation on Closeableinterface states that close method:

Closeable接口上的文档指出关闭方法:

Closes this stream and releases any system resources associated with it.

关闭此流并释放与其关联的所有系统资源。

The releasing system resources includes closing streams.

释放系统资源包括关闭流。

It also states that:

它还指出:

If the stream is already closed then invoking this method has no effect.

如果流已经关闭,则调用此方法无效。

So if you close them explicitly afterwards, nothing wrong will happen.

因此,如果您之后明确关闭它们,则不会发生任何错误。

回答by Dmitry Bychenko

I'd rather use try(...)syntax (Java 7), e.g.

我宁愿使用try(...)语法(Java 7),例如

try (OutputStream outputStream = new FileOutputStream(createdFile)) {
      ...
}

回答by Sivakumar

In Java 7, there is a feature try-with-resources. You no need to explicitly close your streams, it will take care of that.

在 Java 7 中,有一个特性try-with-resources。您无需明确关闭您的流,它会处理这个问题。

回答by Codeversum

It will be fine if you only close the last stream - the close call will be send to the underlying streams, too.

如果您只关闭最后一个流就可以了 - 关闭调用也将发送到底层流。

回答by TheLostMind

No, the topmost level Streamor readerwill ensure that all underlyingstreams / readers are closed.

不,最顶层Streamreader将确保关闭所有底层流/读取器。

Check the close()method implementationof your topmost level stream.

检查顶级流的close()方法实现