web-dev-qa-db-ja.com

ネストされた各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();
126
Adon Smith

すべてのストリームが正常に作成されると仮定すると、はい、それらのストリーム実装でbwを閉じるだけで十分です /;しかし、それは大きな仮定です。

try-with-resourcestutorial )を使用して、例外をスローする後続のストリームを構築する際に以前のストリームがハングしたままにならないようにします。基になるストリームを閉じるための呼び出しを持つストリーム実装に依存する必要はありません。

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

closeをまったく呼び出さないことに注意してください。

重要な注意:try-with-resourcesでそれらを閉じるには、 must ストリームを変数に割り当てるそれらを開くと、ネストを使用できません。ネストを使用する場合、後のストリームの1つ(たとえば、GZIPOutputStream)の構築中に例外が発生すると、その内部のネストされた呼び出しによって構築されたストリームは開いたままになります。 JLS§14.20. から:

Try-with-resourcesステートメントは、variables(リソースと呼ばれる)でパラメーター化され、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");
    }
}
_

...出力があります:

 Example $ InnerMostの構築
 Example $ Middleの構築
 Example $ OuterMostの構築
キャッチブロック内
 Infinallyブロック
メインの

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呼び出しを取得します。

 Example $ InnerMostの構築
 Example $ Middle 
の構築Example $ InnerMost closed 
 catchブロック
 Infinallyブロック
 mainの終わり

(はい、_InnerMost#close_の2つの呼び出しは正しいです。1つはMiddleから、もう1つはtry-with-resourcesからです。)

144
T.J. Crowder

最も外側のストリームを閉じることができます。実際、ラップされたすべてのストリームを保持する必要はなく、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;

ファイルが見つからない場合、閉じるリソースが存在しないため、ファイルを閉じても違いはありません。ファイルが存在する場合、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がトリガーされます。これにより、先ほど見たようにファイルが閉じられます。

12
Peter Lawrey

私はむしろtry(...)構文(Java 7)を使用したいと思います。

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

すべてのストリームがインスタンス化されている場合、最も外側のストリームのみを閉じるだけで十分です。

Closeable インターフェイスに関するドキュメントには、メソッドを閉じることが記載されています。

このストリームを閉じて、それに関連付けられているすべてのシステムリソースを解放します。

システムリソースの解放には、終了ストリームが含まれます。

また、次のことも述べています。

ストリームがすでに閉じている場合、このメソッドを呼び出しても効果はありません。

したがって、後で明示的に閉じた場合、問題は発生しません。

6
Grzegorz Żur

いいえ、最上位のStreamまたはreaderは、すべての基になるストリーム/リーダーが閉じられるようにします。

最上位レベルのストリームのclose()メソッド実装を確認してください。

5
TheLostMind

Java 7では、機能があります try-with-resources 。ストリームを明示的に閉じる必要はありません。

5
Sivakumar

最後のストリームのみを閉じる場合は問題ありません-閉じる呼び出しは、基礎となるストリームにも送信されます。

5
Codeversum