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

我正在写一段代码:

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

我是否需要closures每个stream或作者如下?

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

或者只是closures最后一个stream是好的?

 bw.close(); 

假设所有的stream都被创build好了,是的,只要closuresbw就可以使用这些stream实现 。 但这是一个很大的假设。

我将使用try-with-resources ( tutorial ),以便构造引发exception的后续stream的任何问题都不会使先前的stream停止,因此您不必依靠调用closures的stream实现基础stream:

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

请注意,您不再致电close

重要提示 :要使用资源尝试closures它们,您必须在打开它们时将这些stream分配给variables,但不能使用嵌套。 如果使用嵌套,则在构build其中一个后续stream(例如GZIPOutputStream )期间发生的exception将使任何由嵌套调用构造的stream都处于打开状态。 根据JLS§14.20.3 :

try-with-resources语句是用variables (称为资源)参数化的,它们在执行try块之前被初始化,并且在执行try块之后按照与它们初始化相反的顺序自动closures。

请注意“variables” (我强调)

例如,不要这样做:

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

…因为来自GZIPOutputStream(OutputStream)构造函数(表示可能抛出IOException ,并将头部写入基础stream)的exception将使FileOutputStream处于打开状态。 由于某些资源的构造函数可能会抛出,而其他资源则不会,因此将它们分开列出是个好习惯。

我们可以用这个程序仔细检查我们对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"); } } 

有输出的:

构造示例$ InnerMost
构造示例$中
构造示例$ OuterMost
在catch块
在终于阻止
在主要结束

请注意,没有电话close

如果我们修正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"); } 

那么我们会得到适当的close电话:

构造示例$ InnerMost
构造示例$中
构造示例$ OuterMost
示例$中间closures
示例$ InnerMostclosures
示例$ InnerMostclosures
在catch块
在终于阻止
在主要结束

(是的,关于InnerMost#close两个调用是正确的,一个来自Middle ,另一个来自试用资源。)

你可以closures最外层的stream,实际上你不需要保留所有的stream,你可以使用Java 7的try-with-resources。

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

如果您订阅YAGNI,或者您不需要,您应该只添加您实际需要的代码。 你不应该添加你认为可能需要的代码,但实际上并没有做任何有用的事情。

拿这个例子来想象一下,如果你不这样做,会产生什么样的影响呢?

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

让我们从调用open FileOutputStream开始做所有的实际工作。

 /** * 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; 

如果没有find该文件,则没有底层资源closures,因此closures该文件不会有任何区别。 如果该文件存在,它应该抛出一个FileNotFoundException。 所以试图从这条线上单独closures资源是没有任何好处的。

您需要closures文件的原因是文件成功打开,但以后出现错误。

让我们看看下一个streamGZIPOutputStream

有代码可以抛出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) }); } 

这写入文件的标题。 现在打开一个文件进行写入,但是不能写8个字节的文件是非常不寻常的,但是让我们想象这会发生,然后我们不closures文件。 如果文件没有closures,会发生什么?

你没有得到任何未刷新的写入,它们被丢弃,在这种情况下,没有成功写入字节到这个时候没有被caching的stream。 但是一个没有closures的文件并不是永远的,而是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(); } } } 

如果你根本没有closures文件,它就会被closures,而不是立即(就像我说的那样,留在缓冲区中的数据将会以这种方式丢失,但是在这一点上是没有的)

什么是不立即closures文件的后果? 在正常情况下,您可能会丢失一些数据,并有可能用完文件描述符。 但是如果你有一个系统可以创build文件,但是你不能写任何东西给他们,那么你就有更大的问题。 即很难想象,为什么你反复尝试创build这个文件,尽pipe你失败了。

OutputStreamWriter和BufferedWriter都不会在其构造函数中抛出IOException,所以不清楚它们会造成什么问题。 在BufferedWriter的情况下,你可能会得到一个OutOfMemoryError。 在这种情况下,它会立即触发一个GC,正如我们所看到的,无论如何将closures文件。

如果所有的stream都被实例化了,那么只closures最外层就好了。

可closures接口上的文档声明closures方法:

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

释放系统资源包括closuresstream。

它还指出:

如果stream已经closures,则调用此方法不起作用。

所以如果你事后明确地closures它们,没有什么不对的。

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

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

如果您只closures最后一个stream,那么也可以,closures呼叫也会发送到基础stream。

不,最顶层的Streamreader将确保所有基础stream/读取器都closures。

检查最顶层的stream的close()方法实现

在Java 7中,有一个function尝试与资源 。 你不需要明确closures你的stream,它会照顾到这一点。