読者です 読者をやめる 読者になる 読者になる

java.io.InputStream を複数のファイル読み込みで共有しない

java.util.zip.ZipOutputStream による ZIP アーカイブについて調べてたら、以下のようなコードを見かけました。(適当に抜粋してます)

gist.github.com

単純に、ディレクトリ配下のファイルを commons-io で取得して、ZIP にアーカイブするコードです。これ自体は正しく動作するし、目的通りに ZIP ファイルが生成されます。
しかし、呼び出し元が以下のような感じだったら。

archive(dirPath);
FileUtils.deleteDirectory(new File(dirPath));

要するに、ZIP ファイルを生成したあと、もとのディレクトリを削除するような場合。このとき、FileUtils#deleteDirectory でエラーが発生します。

java.io.IOException: Unable to delete file: ...
        at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:2192)
        at org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1585)
        at org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:1467)
        at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:2183)
        at org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1585)
        at org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:1467)
        at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:2183)
        at org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1585)
        at org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:1467)
        at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:2183)
        at org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1585)
        at org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:1467)
        ...

冒頭のコードだと、writeZipEntry メソッドに File オブジェクトの配列を渡して、同じ InputStream の参照でそれぞれのファイルを読み込んでいます。最後に finally で InputStream を close していますが、最後に処理したファイルのストリームしか close されていないのではないかと推測しています。そのため、後続のディレクトリの削除処理でエラーが発生しているのではないか、というわけです。(自信はない…)

冒頭のコードを以下のように変更すると、先のエラーは発生しなくなります。
要するに、処理するファイルごとに writeZipEntry メソッドを呼び出して、毎回 InputStream を close するようにします。

gist.github.com

java.io.InputStream は複数のファイル読み込みで共有せず、ファイル読み込みごとに close すること。

余談

ちなみに冒頭のコードですが、writeZipEntry メソッドの最後で System#gc を呼び出すとエラーが発生しなくなります。うーむ…。