QA@IT

XMLパーサにて例外発生後、ファイルがロックされてしまう事象について

5589 PV

Javaについて質問させて頂きます。

XMLパーサにて例外が発生後した後、読み込んだファイルを削除したいのですが、ファイルがロックされてしまっており、削除に失敗してしまう事象が発生しています。

環境:JDK1.6.0_29
OS:WindowsXP

// パーステストクラス
public class ParseTest {

    File mFile = null;

    // コンストラクタ
    public ParseTest(File pFile) {
        mFile = pFile;
    }

    // パース処理
    public void parse() throws Exception {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        DocumentBuilder builder = null ;
        Document document = null ;

        try {
            builder = factory.newDocumentBuilder();

        try {
            document = builder.parse( mFile );

        } catch (ParserConfigurationException e) {
            throw e;

        } catch (IOException e) {
            throw e;

        } catch (SAXException e) {
            throw e;
        }
    }
}

上記のパーステストクラスにて、parseメソッドの引数に指定するXMLは、わざと例外が発生するようにしています。
(encodingを"UTF-8"と定義し、ファイルの文字コードはShift-JISとする)

以下のテストクラスではパーステストクラスを呼出しており、catchの中にてparseに失敗したファイルをFile#deleteにて削除しようとしていますが、削除に失敗します。デバッグモードにてcatchの途中で止めた状態でWindows上での削除も同様に失敗します。
mainメソッド終了後はWindowsでの削除は成功します。

// テストクラス
public class Test {

    public static void main(String[] args) {

        File file = new File("/tmp/hoge.xml)";

        try {
            ParseTest test = new ParseTest(file);

            test.parse();

    // 例外発生
        } catch (Exception e) {
            e.printStackTrace() ;

            boolean result = file.delete();

            // 削除結果がfalseとなってしまう
            System.out.println("削除結果=" + result);
        }
    }
}

検証として、テストクラスのcatchの中にてループさせてみました。

// 例外発生
} catch (Exception e) {

    int cnt = 0;
    boolean flg = false;
    while (!flg) {

        boolean rst = file.delete();
        System.out.println(cnt++);
        if (rst) {
            flg = true;

    }
}

実行した結果、6000回ほどループしたところで
delete実行結果がtrueとなりました。

推測すると、XMLパーサ関連クラスがすぐにファイルロックを開放しない為、
GCにて開放されるまでロックし続けてしまうのではないかと考えました。

上記のwhile文は使わずに正しいロック開放出きればよいのですが、
該当するようなメソッドが見つけられませんでした。

同じ様な事象を経験して解決されたお方、もしくは解決の糸口となるような
情報をお持ちの方がいらっしゃいましたらご教授頂けますでしょうか。

宜しくお願い致します。

回答

DocumentBuilder#parse(File) ではなく、DocumentBuilder#parse(InputStream) を利用してはどうでしょうか。
ファイルハンドルが残ってしまう原因はわかりませんが、InputStream であれば確実にファイルを開放できるようにコントロールできるかと思います。

ご質問にあるコードを直すのであれば、

    public void parse() throws Exception {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        DocumentBuilder builder = null;
        Document document = null;
        InputStream is = null;

        try {
            builder = factory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw e;
        }
        try {
            is = new FileInputStream(mFile);
            document = builder.parse(is);
        } catch (IOException e) {
            throw e;

        } catch (SAXException e) {
            throw e;

        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // nop
                }
            }
        }
    }

といった感じでしょうか。

編集 履歴 (1)
  • ご提案して頂いた方法を試してみました。
    parseの引数をFileからFileInputStreamに変更することでロックが開放され、削除する事が出来ました。
    ありがとうございました。
    -

parseの引数をFileからFileInputStreamに変更すること

ではなく、finallyis.close()するようにしたからでしょう。
Windowsでは開いたままのファイルは削除できません。

編集 履歴 (0)

そのコードを実行しましたが、こちらの環境では症状が再現しませんでした。
削除結果=trueになります。

環境:JDK1.6.0_31
OS:MacOS X 10.7.4

DocumentBuilder#reset()InputStream#close()などは、もう試しましたか?

編集 履歴 (0)
  • わざわざ試して頂いてありがとうございました。
    再現しなかったのはOSの違いによるものなのかも知れません。
    DocumentBuilder#reset()については成功しませんでした。

    InputStream#close()については有効でした。
    最初に回答された方の方法を参考として解決しましたので、解決は最初の方の方にチェックさせて頂きました。

    ありがとうございました。
    -
  • 「最初の回答」は、投稿時刻を見ると分かります。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。