QA@IT

Java の XML 解析で例外が発生する。

6597 PV

Java の org.w3c.dom 関連を使用して外部 XML ファイルをプログラム内の DOM オブジェクトに読み取ろうとしています。
OS は Windows8 (64bit) で JDK のバージョンは 1.7.0_17。
小さな XML ファイルは問題無く読み取ることができるのですが、35MB ほどある大きな XML ファイルを読み取ろうとすると、次のような例外を発生してしまいます。
この例外発生を回避する方法をご存じの方居らっしゃいませんか?

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 8192
    at com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.read(UTF8Reader.java:546)
    at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load(XMLEntityScanner.java:1753)
    at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.arrangeCapacity(XMLEntityScanner.java:1629)
    at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.skipString(XMLEntityScanner.java:1667)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1708)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2900)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:607)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:489)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:835)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:123)
    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:237)
    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:300)
    at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:121)
    at hogehoge.HOGEHOGE(HOGEHOGE.java:1418)

ソースでは

DocumentBuilder documentBuilder = ...;
FileInputStream is = ...;

Document document = documentBuilder.parse(is);

の部分にあたります。
これは、同一 XML ファイルを MacOS (MountainLion) 上で読み取るときには例外は発生していません。 Linux では試していません。
DocumentBuilder#parse() ではなく、 LSParser#parse() を使用しても同様の例外を発生します。

スタック トレース上の com.sun.org.apache.xerces.internal.impl.io.UTF8Reader のソースを追ってみましたが、どうも com.sun.org.apache.xerces.internal.impl.XMLEntityScanner でファイルの行の文字バッファとして XMLEntityManager で定義されている DEFAULT_BUFFER_SIZE = 8192 というバッファ サイズをオーバーしてアクセスしているようです。
おそらく XML ファイルのある行が 8192 文字を超えてしまっているなどのことで発生しているのかと推測しますが、Windows で発生して MacOS で発生しない理由がよくわかりません。また、DocumentBuilder などのクラスで、このバッファー サイズを増減させる方法もわかっておりません。

MacOS X と Windows の違いというと改行コードか、ということで System.setProperty("line.separator", "\n"); また VM のオプションに -Dline.separator="\n" を行ってみましたが、変化はありませんでした。

以上、よろしくお願いいたします。

回答

自己解決できたようです。

FileInputStream is = ...;
Document document = documentBuilder.parse(is);

というように直接バイナリの入力ストリームを parser にかけていたのですが、これを

FileInputStream is = ...;
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
InputSource source = new InputSource(isr);
Document document = documentBuilder.parse(source);

とすることで、例外が発生しないようになりました。 DOM でなく SAX についても同様です。
javax.xml.parsers 関連は Apache の Xerces からひっぱってきていると思われますが、この内部でバイナリー→テキスト変換をするところがおそらくバグっているのではないかと思われます。
バイナリー→テキスト変換を java.io.InputStreamReader の方に任せることによって回避できたのではないかと考えます。

crmlpudding さん、コメントありがとうございました。確かにメモリを食うのも問題ではあるので、少し検討してみたいと思います。

編集 履歴 (1)

DOMの操作っていうのは、ツリーのように扱いたいってことでしょうか?
SAXで読込み時に自作するのがいいとは思うのですが。。。
DOMはメモリ喰いますよ。

そもそもの原因を見落としてました。
エラーの内容が、存在しない配列にアクセスしたときのものですね。
配列の数を確認してみてください。

編集 履歴 (0)
  • # すみません。さきほどはコメントで書きこめなかったようです。

    ツリー構造を手動でいじり、またそれを XML にもどしたりを行いいます。
    やってみましたが SAXParser.parse() は同様の例外を発生します。
    テキスト行の解析のための文字配列の数が 8192 でそれ以上のところにアクセスしている例外です。Apache Xerces のバグではないでしょうか。
    -

ご回答ありがとうございます。
XML を読みこんだ後に DOM 操作がしたいため、SAX は敢えて使っていない感じです。
memory overflow などであればそちらの方向への回避は考えるのですが、そうではないため、どうにか DOM の読みこみの形のままで何とかならないかしら、というところです。

ちなみに web の QA サイトを漁っていると、直接 XML を読みこむところではないのですが UTF8Reader の "java.lang.ArrayIndexOutOfBoundsException: 8192" で困っている人は居るようです。ただ、解決策が提示されるケースはまだ無いようです。

編集 履歴 (2)
ウォッチ

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