Нужно ли закрывать каждый вложенный OutputStream и Writer отдельно?

Я пишу код:

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

Нужно ли закрывать каждый stream или писатель, как показано ниже?

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

Или просто закрыть последний stream будет хорошо?

 bw.close(); 

    Предполагая, что все streamи создаются в порядке, да, просто закрытие bw отлично подходит для этих реализаций streamа ; но это большое предположение.

    Я бы использовал try-with-resources ( учебник ), чтобы любые проблемы, связанные с созданием последующих streamов, которые генерируют исключения, не оставляют висящие предыдущие streamи, и поэтому вам не нужно полагаться на реализацию streamа, имеющую вызов закрыть базовый stream:

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

    Обратите внимание, что вы больше не вызываете close .

    Важное примечание . Чтобы закрыть их, вы должны назначить streamи переменным при их открытии, вы не можете использовать вложенность. Если вы используете вложенность, исключение при построении одного из более поздних streamов (скажем, GZIPOutputStream ) оставит любой stream, построенный вложенными вызовами внутри него открытым. Из JLS §14.20.3 :

    Оператор try-with-resources параметризуется переменными (называемыми ресурсами), которые инициализируются перед исполнением блока try и автоматически закрываются в обратном порядке, из которого они были инициализированы, после выполнения блока try .

    Обратите внимание на слово «переменные» (мой акцент) .

    Например, не делайте этого:

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

    … потому что исключение из конструктора GZIPOutputStream(OutputStream) (который говорит, что может GZIPOutputStream(OutputStream) исключение IOException и записывает заголовок в базовый stream), оставит 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
     Пример $ Средний закрыт
     Пример $ InnerMost закрыт
     Пример $ InnerMost закрыт
     В блоке catch
     В окончательном блоке
     В конце основного
    

    (Да, два вызова InnerMost#close верны, один из них – Middle , другой – из try- InnerMost#close -resources.)

    Вы можете закрыть внешний 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) ) { // ... } 

    Давайте начнем с 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; 

    Если файл не найден, базовый ресурс не будет закрыт, поэтому его закрытие не будет иметь никакого значения. Если файл существует, он должен вызывать исключение FileNotFoundException. Поэтому ничего не получается, пытаясь закрыть ресурс только из этой линии.

    Причина, по которой вам нужно закрыть файл, – это когда файл открывается успешно, но вы позже получите сообщение об ошибке.

    Давайте посмотрим на следующий stream GZIPOutputStream

    Есть код, который может вызывать исключение

     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 байтов, но представьте себе, что это может произойти, и мы не закрываем файл впоследствии. Что происходит с файлом, если он не закрыт?

    Вы не получаете никаких незакрепленных записей, они отбрасываются, и в этом случае в stream не записаны успешно записанные байты, которые в любом случае не буферизуются. Но файл, который не закрыт, не живет вечно, вместо этого 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(); } } } 

    Если вы вообще не закрываете файл, он все равно закрывается, просто не сразу (и, как я уже сказал, данные, оставшиеся в буфере, будут потеряны таким образом, но на данный момент их нет)

    Каково последствие немедленного закрытия файла? В нормальных условиях вы можете потерять некоторые данные, и вы, возможно, закончите файловые дескрипторы. Но если у вас есть система, где вы можете создавать файлы, но вы ничего не можете им писать, у вас есть большая проблема. т.е. трудно представить, почему вы неоднократно пытаетесь создать этот файл, несмотря на то, что вы терпите неудачу.

    Оба OutputStreamWriter и BufferedWriter не бросают IOException в свои конструкторы, поэтому неясно, какую проблему они могут вызвать. В случае с BufferedWriter вы можете получить OutOfMemoryError. В этом случае он немедленно вызовет GC, который, как мы видели, все равно закроет файл.

    Если все streamи были созданы, то закрытие только самого внешнего вполне нормально.

    В документации по интерфейсу Closeable указано, что метод close:

    Закрывает этот stream и освобождает связанные с ним системные ресурсы.

    Ресурсы системы выпуска include в себя закрывающие streamи.

    В нем также говорится, что:

    Если stream уже закрыт, вызов этого метода не имеет никакого эффекта.

    Поэтому, если вы закрываете их явно после этого, ничего плохого не произойдет.

    Я предпочел бы использовать синтаксис try(...) (Java 7), например

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

    Это будет хорошо, если вы только закроете последний stream – звонок будет также отправлен в базовые streamи.

    Нет, верхний уровень Stream или reader гарантирует, что все основные streamи / считыватели будут закрыты.

    Проверьте реализацию метода close() вашего верхнего уровня.

    В Java 7 есть функция try-with-resources . Вам не нужно явно закрывать streamи, это позаботится об этом.

    Давайте будем гением компьютера.