Javaでネストされたストリームとライターを閉じる正しい方法[重複]

StackOverflow https://stackoverflow.com/questions/884007

  •  22-08-2019
  •  | 
  •  

質問

注記: この質問とその回答のほとんどは、Java 7 のリリース前に作成されたものです。Java 7 が提供するのは、 自動リソース管理 これを簡単に行うための機能。Java 7 以降を使用している場合は、次のステップに進む必要があります。 ロス・ジョンソンの答え.


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

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

close 操作を保証する必要があることを理解しています (おそらく、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()は呼び出されません。したがって、ファイルを開いたままにしているFOを閉じることを主に心配するのは「最も安全」(ただし醜い)のようです。


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

また、これについて詳しく説明した Java/Sun の公式ドキュメントはありますか?

役に立ちましたか?

解決

私は通常次のことを行います。まず、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 自体を閉じようとします。(ストリームがすでに閉じられている場合、Closeable の定義は close() を呼び出して呼び出しを無視することに注意してください)

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

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 と、それが使用する「保留中」変数のアプローチに従うことを検討してください。

他のヒント

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

参照する Java I/O ストリーム 詳細については。

問題に対処するには

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

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

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

Java 7 の時代には、 リソースを試す それは確かに進むべき道です。以前のいくつかの回答で述べたように、close リクエストは最も外側のストリームから最も内側のストリームに伝播します。したがって、必要なのは 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 をスローする可能性があり、これは終了せずにアプリケーションによって処理される可能性があります。多くのストリーム クラスは未チェックの例外のみをスローするため、アプリケーションが終了する可能性があります。

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);
}

同僚は興味深い論点を提起しており、どちらにしても議論の余地はあります。

個人的には無視しますが、 RuntimeException, チェックされていない例外はプログラムのバグを示しているためです。プログラムが間違っている場合は修正してください。悪いプログラムを実行時に「処理」することはできません。

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

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

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

デコレータの中には実際にリソースを持っている人もいます。現在の Sun の実装では、 ZipInputStream たとえば、非 Java ヒープ メモリが割り当てられています。

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

一方 BufferedOutputStream 日でも閉まります IOException から flush, BufferedWriter 正しく閉まります。

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

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

(見て:キャッチはありません!)

そしておそらく、Execute Around イディオムを使用します。

Java SE 7 リソースを試す 言及されていないようだ。これにより、明示的にクローズを行う必要が完全になくなり、私はこのアイデアがとても気に入っています。

残念ながら、Android 開発の場合、この機能は Android Studio (だと思う) を使用することによってのみ利用可能になります。 キットカット以上を対象とする.

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

これをチェックしてhttp://ckarthik17.blogspot.com/2011/02/closed-nested-streams.html

私はよくこのようにストリームを閉じていましたが、 ネストせずに try-catch を最終的に ブロック

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) {}
    }
  } 
}

Sun の JavaDocs には以下が含まれます RuntimeExceptionは、InputStream のドキュメントに示されているように、 read(byte[], int, int) 方法;NullPointerException および IndexOutOfBoundsException をスローするものとして文書化されています。

FilterOutputStream の 流す() IOException をスローすることだけが文書化されているため、実際には何もスローされません。 RuntimeExceptions.投げられる可能性のあるものは、おそらく、 IIOException.

まだ投げられる可能性があります Error, しかし、これらについてできることはあまりありません。サン氏は、彼らを捕まえようとしないことをお勧めします。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top