2015-02-02 3 views
113

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

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

Мне нужно закрыть каждый поток или писатель, как следующий делать?

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

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

bw.close(); 
+1

Для соответствующего устаревшего вопроса Java 6 см. Http://stackoverflow.com/questions/884007/best-way-to-close-nested-streams-in-java-6 – Raedwald

+1

Обратите внимание, что ваш пример имеет ошибку, которая может привести к потере данных, поскольку вы закрываете потоки не в том порядке, в котором вы их открыли. При закрытии «BufferedWriter» может потребоваться записать * буферизованные * данные в базовый поток, который в вашем примере уже закрыт. Избежать этих проблем является еще одним преимуществом подходов * try-with-resource *, показанных в ответах. – Joe23

ответ

134

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

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

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

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

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

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

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

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

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

... потому что исключение из GZIPOutputStream(OutputStream) конструктора (который говорит, что он может бросить IOException, и записывает заголовок в основной поток) оставил бы 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"); 
    } 
} 

... который имеет выход:

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

Обратите внимание, что не существует никаких вызовов к 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 вызовы:

 
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 

(Да, два вызова InnerMost#close является правильным, один из Middle, другой из попытки -with-resources.)

+6

+1 за то, что исключение может быть брошено во время строительства потоков, хотя я хочу заметить, что реально вы либо собираетесь выйти извне, исключение из памяти или что-то столь же серьезное (в какой момент это действительно не имеет значения, если вы закрываете свои потоки, потому что ваше приложение собирается выйти), или это будет GZIPOutputStream, который генерирует исключение IOException; остальные конструкторы не имеют проверенных исключений, и нет других обстоятельств, которые могут вызвать исключение во время выполнения. – Jules

+5

@Jules: Да, для этих конкретных потоков. Это больше о хороших привычках. –

+1

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

6

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

документация на Closeable интерфейс заявляет, что закрытым способом:

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

Ресурсы системы выпуска включают в себя закрывающие потоки.

Он также утверждает, что:

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

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

+2

Это не предполагает ошибок * построения * потоков, которые могут быть или не быть истинными для перечисленных в списке, но в целом не являются ** достоверными. –

5

Это будет нормально, если вы только закроете последний поток - звонок будет отправлен и в базовые потоки.

+1

Посмотрите комментарий от ответа Гжегожа Жюра. –

6

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

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

Пока я согласен с вами, вы можете выделить преимущество этого подхода и ответить на этот вопрос, если OP должен закрыть дочерние/внутренние потоки. – MadProgrammer

12

Вы можете закрыть внешний наиболее поток, на самом деле вам не нужно сохранять все потоки завернуты, и вы можете использовать Java 7 примерочных с-ресурсов.

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. Поэтому ничего не получается, пытаясь закрыть ресурс только из этой линии.

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

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

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

+1

См. T.J. Ответ Краудера для ситуаций, когда это может потерпеть неудачу. – TimK

+0

@TimK вы можете указать пример создания файла, но позже поток завершится неудачей и каков результат. Риск выхода из строя крайне низок, и воздействие тривиально. Не нужно делать сложнее, чем нужно. –

+0

@TimK Его ответ фактически не говорит, где он может потерпеть неудачу, просто, что он отличается от другого. –

5

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

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

Смежные вопросы