web-dev-qa-db-ja.com

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

注:この質問とその回答の大部分は、Java 7. Java 7は 自動リソース管理 を提供します。これを簡単に行うための機能です。Java 7以降を使用している場合は 答えロス・ジョンソンの


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の公式ドキュメントはありますか?

92
dirtyvagabond

私は通常以下を行います。まず、try/catch messを処理するテンプレートメソッドベースのクラスを定義します

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がある場合、最初にBuffereredWriterを閉じようとします。 (ストリームがすでに閉じられている場合、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およびそれが使用する「保留」変数アプローチに従うことを検討してください。

19

チェーンストリームを閉じるときは、最も外側のストリームのみを閉じる必要があります。エラーはチェーンに伝播され、キャッチされます。

詳細については、 Java I/O Streams を参照してください。

問題に対処するには

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

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

同僚はRuntimeExceptionについて良い点を指摘しています。ストリームを絶対に閉じる必要がある場合は、外部から最初の例外で停止して、それぞれを個別にいつでも閉じることができます。

39
Bill the Lizard

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

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をスローできます。IOExceptionは、終了せずにアプリケーションで処理される可能性があります。多くのストリームクラスは、未チェックの例外のみをスローします。これにより、アプリケーションが終了する可能性があります。

26
Ross Johnson

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);
}
21
Rulix Batistil

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

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

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

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

(IIRC)Javaライブラリで使用されるリソースの3分の2が明らかに正しくない方法で実装されていると主張されています。

BufferedOutputStreamIOExceptionからの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イディオムを使用してください。

同僚は興味深い点を提起し、どちらの方法でも議論する根拠があります。

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

5
erickson

Java SE 7 try-with-resources は言及されていないようです。明示的に完全にクローズする必要がなくなるため、このアイデアは非常に気に入っています。

残念ながら、Android開発では、このスイートはAndroid Studio(と思う)および KitKat以上をターゲットとする を使用することによってのみ利用可能になります。

1
akauppi

私はこのようなストリームを閉じるために使用します最終的に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) {}
    }
  } 
}
0
Noor Nawaz

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

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

0