96

注:この質問とその回答のほとんどは、Java 7 のリリース前にさかのぼります。Java 7 は、これを簡単に行うための自動リソース管理機能を提供します。Java 7 以降を使用している場合は、Ross Johnson の回答に進む必要があります。


Javaでネストされたストリームを閉じるための最良かつ最も包括的な方法と考えられているものは何ですか? たとえば、次のセットアップを考えてみましょう。

FileOutputStream fos = new FileOutputStream(...)
BufferedOS bos = new BufferedOS(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);

閉じる操作を保証する必要があることを理解しています(おそらくfinally句を使用して)。私が疑問に思っているのは、ネストされたストリームが閉じられていることを明示的に確認する必要があるのか​​、それとも外側のストリーム (oos) を確実に閉じるだけで十分なのかということです。

少なくともこの特定の例を扱って気付いたことの 1 つは、内部ストリームが FileNotFoundExceptions のみをスローしているように見えることです。これは、失敗した場合にそれらを閉じることを技術的に心配する必要がないことを暗示しているようです。

同僚が書いたものは次のとおりです。


技術的には、正しく実装されていれば、最も外側のストリーム (oos) を閉じるだけで十分です。しかし、実装には欠陥があるようです。

例: BufferedOutputStream は、次のように定義されている FilterOutputStream から close() を継承します。

 155       public void close() throws IOException {
 156           try {
 157             flush();
 158           } catch (IOException ignored) {
 159           }
 160           out.close();
 161       }

ただし、flush() が何らかの理由で実行時例外をスローした場合、out.close() は呼び出されません。そのため、ファイルを開いたままにしている FOS を閉じることを主に心配するのが "最も安全" (しかし見苦しい) ようです。


ネストされたストリームを閉じるための、絶対に確認する必要がある場合の最善のアプローチと考えられるものは何ですか?

そして、これを詳細に扱っている公式の Java/Sun ドキュメントはありますか?

4

10 に答える 10

41

連鎖ストリームを閉じるときは、最も外側のストリームを閉じるだけで済みます。すべてのエラーはチェーンを伝播し、捕捉されます。

詳細については、 Java I/O ストリームを参照してください。

問題に対処するには

ただし、flush() が何らかの理由で実行時例外をスローした場合、out.close() は呼び出されません。

これは正しくありません。その例外をキャッチして無視すると、catch ブロックの後に実行が再開され、out.close()ステートメントが実行されます。

あなたの同僚は、ランタイム例外について良い点を指摘しています。どうしてもストリームを閉じる必要がある場合は、最初の例外で停止して、外側から内側へ、いつでもそれぞれを個別に閉じようとすることができます。

于 2009-05-19T17:17:45.603 に答える
32

Java 7 の時代では、try-with-resourcesが確実に進むべき道です。以前のいくつかの回答で述べたように、クローズ リクエストは最も外側のストリームから最も内側のストリームに伝播します。したがって、必要なのは 1 回のクローズだけです。

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
  // do something with ois
}

ただし、このパターンには問題があります。try-with-resources は内部の FileInputStream を認識しないため、ObjectInputStream コンストラクターが例外をスローした場合、FileInputStream は (ガベージ コレクターが到達するまで) 閉じられません。解決策は...

try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) {
  // do something with ois
}

これはエレガントではありませんが、より堅牢です。これが実際に問題になるかどうかは、外部オブジェクトの構築中にスローされる例外によって異なります。ObjectInputStream は IOException をスローする可能性があり、終了せずにアプリケーションによって処理される可能性があります。多くのストリーム クラスは未チェックの例外のみをスローするため、アプリケーションが終了する可能性があります。

于 2014-02-12T09:06:32.093 に答える
22

Apache Commons を使用して IO 関連のオブジェクトを処理することをお勧めします。

finally句の使用IOUtils

IOUtils.closeQuietly(bWriter); IOUtils.closeQuietly(oWritter);

以下のコード スニペット。

BufferedWriter bWriter = null;
OutputStreamWriter oWritter = null;

try {
  oWritter  = new OutputStreamWriter( httpConnection.getOutputStream(), "utf-8" );
  bWriter = new BufferedWriter( oWritter );
  bWriter.write( xml );
}
finally {
  IOUtils.closeQuietly(bWriter);
  IOUtils.closeQuietly(oWritter);
}
于 2011-03-16T16:06:26.007 に答える
19

私は通常、次のことを行います。まず、テンプレート メソッド ベースのクラスを定義して、try/catch の混乱を処理します。

import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public abstract class AutoFileCloser {
    // the core action code that the implementer wants to run
    protected abstract void doWork() throws Throwable;

    // track a list of closeable thingies to close when finished
    private List<Closeable> closeables_ = new LinkedList<Closeable>();

    // give the implementer a way to track things to close
    // assumes this is called in order for nested closeables,
    // inner-most to outer-most
    protected final <T extends Closeable> T autoClose(T closeable) {
            closeables_.add(0, closeable);
            return closeable;
    }

    public AutoFileCloser() {
        // a variable to track a "meaningful" exception, in case
        // a close() throws an exception
        Throwable pending = null;

        try {
            doWork(); // do the real work

        } catch (Throwable throwable) {
            pending = throwable;

        } finally {
            // close the watched streams
            for (Closeable closeable : closeables_) {
                if (closeable != null) {
                    try {
                        closeable.close();
                    } catch (Throwable throwable) {
                        if (pending == null) {
                            pending = throwable;
                        }
                    }
                }
            }

            // if we had a pending exception, rethrow it
            // this is necessary b/c the close can throw an
            // exception, which would remove the pending
            // status of any exception thrown in the try block
            if (pending != null) {
                if (pending instanceof RuntimeException) {
                    throw (RuntimeException) pending;
                } else {
                    throw new RuntimeException(pending);
                }
            }
        }
    }
}

「保留中」の例外に注意してください。これは、クローズ中にスローされた例外が、本当に気にかけている可能性のある例外をマスクする場合に対処します。

最終的には、装飾されたストリームの外側から最初に閉じようとします。そのため、FileWriter をラップする BufferedWriter がある場合は、最初に BufferedWriter を閉じようとし、それが失敗した場合でも、FileWriter 自体を閉じようとします。(ストリームが既に閉じられている場合、close() の呼び出しを無視するための Closeable 呼び出しの定義に注意してください)

上記のクラスは次のように使用できます。

try {
    // ...

    new AutoFileCloser() {
        @Override protected void doWork() throws Throwable {
            // declare variables for the readers and "watch" them
            FileReader fileReader = 
                    autoClose(fileReader = new FileReader("somefile"));
            BufferedReader bufferedReader = 
                    autoClose(bufferedReader = new BufferedReader(fileReader));

            // ... do something with bufferedReader

            // if you need more than one reader or writer
            FileWriter fileWriter = 
                    autoClose(fileWriter = new FileWriter("someOtherFile"));
            BufferedWriter bufferedWriter = 
                    autoClose(bufferedWriter = new BufferedWriter(fileWriter));

            // ... do something with bufferedWriter
        }
    };

    // .. other logic, maybe more AutoFileClosers

} catch (RuntimeException e) {
    // report or log the exception
}

このアプローチを使用すると、try/catch/finally について心配する必要がなくなり、ファイルを再度閉じることができます。

これが重すぎて使用できない場合は、少なくとも、try/catch とそれが使用する「保留中」の変数アプローチに従うことを検討してください。

于 2009-05-19T17:53:50.583 に答える
5

これは驚くほど厄介な質問です。acquire; try { use; } finally { release; }(コードが正しいと仮定しても。)

デコレータの構築が失敗した場合、基になるストリームを閉じることはありません。したがって、最終的に使用した後でも、リソースをデコレータに正常に渡した後でも、基になるストリームを明示的に閉じる必要があります)。

例外によって実行が失敗した場合、本当にフラッシュしますか?

一部のデコレーターは、実際にリソース自体を持っています。ZipInputStreamたとえば、Sun の現在の実装には、Java 以外のヒープ メモリが割り当てられています。

(IIRC) Java ライブラリで使用されるリソースの 3 分の 2 は、明らかに間違った方法で実装されていると主張されています。

fromBufferedOutputStreamでも閉じますIOExceptionflushBufferedWriter正しく閉じます。

私のアドバイス: リソースをできるだけ直接閉じて、他のコードを汚染させないでください。OTOH、この問題に多くの時間を費やすことができます -OutOfMemoryErrorがスローされた場合、適切に動作するのは良いことですが、プログラムの他の側面はおそらく優先度が高く、とにかくこの状況ではライブラリ コードが壊れている可能性があります。しかし、私はいつも次のように書いています。

final FileOutputStream rawOut = new FileOutputStream(file);
try {
    OutputStream out = new BufferedOutputStream(rawOut);
    ... write stuff out ...
    out.flush();
} finally {
    rawOut.close();
}

(ほら: 引っかからない!)

そして、おそらく Execute Around イディオムを使用してください。

于 2009-05-19T18:05:47.490 に答える
0

また、ネストされたすべてのストリームを閉じる必要はありません

これを確認して くださいhttp://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html

于 2011-02-06T14:18:06.143 に答える
0

私は、try-catch を finallyブロックに入れ子にせずに、このようにストリームを閉じていました。

public class StreamTest {

public static void main(String[] args) {

    FileOutputStream fos = null;
    BufferedOutputStream bos = null;
    ObjectOutputStream oos = null;

    try {
        fos = new FileOutputStream(new File("..."));
        bos = new BufferedOutputStream(fos);
        oos = new ObjectOutputStream(bos);
    }
    catch (Exception e) {
    }
    finally {
        Stream.close(oos,bos,fos);
    }
  }   
}

class Stream {

public static void close(AutoCloseable... array) {
    for (AutoCloseable c : array) {
        try {c.close();}
        catch (IOException e) {}
        catch (Exception e) {}
    }
  } 
}
于 2016-02-25T10:00:43.803 に答える