目次


ヒント: 巨大なXML文書を出力する

第1 回

現在のさまざまなXML出力オプションに関する調査

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: ヒント: 巨大なXML文書を出力する

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:ヒント: 巨大なXML文書を出力する

このシリーズの続きに乞うご期待。

XMLの分野でよく見られる問題の1つは、大きな文書の出力です。XMLをどのように読み取るべきかについてはかなり理解が進んできましたが、XMLの出力に関しては、ベスト・プラクティスがほとんど確立されていません。出力の規模が比較的小さい場合 (たとえば1,000レコード未満 の場合) には、大した問題ではありません。開発者たちはDOMやJDOMのようなAPIを使用するか、入出力ストリームを使って生 (なま) の文字ベースのXMLを出力すればいいのです。しかし、出力されるデータ・セットの数が何千、いや何万にも膨れ上がると、こうしたソリューションでは太刀打ちできません。今回のヒントではこうした問題について解説し、利用可能な代替策を示し、XML出力を完全にカバーするためのプランを提示します。

代替的な出力方法

XMLを出力する方法として、いくつかのオプションがあります。出力のためのソリューションを探し始める前に、まず、あなたが使用すべきでない ソリューションとはどんなものか、説明しましょう。XMLを出力する際の 最も一般的な方法 は、以下のとおりです。

  1. SAX
  2. DOM
  3. JAXP
  4. 別のインメモリーAPI (たとえばJDOMやdom4j)
  5. ローI/Oストリーム (生の入出力ストリーム)

まず、これらの方法について1つずつ見ていきましょう。その後、今後のヒント・シリーズで、ある1つのソリューションを取り上げます。

SAX

最初のオプションSAXは、本当はオプションではありません。私がこれをリストに含めた理由は、SAXがXMLを高速処理できるという評判が広まっていて、XMLの世界に入ったばかりのほとんどの開発者がSAXに関心を持つためです。確かに、SAX はXML処理用の最も高速かつ最も簡単なAPIだと考えられていますが、XMLを出力する能力はまったくありません (その他の機能もありません)。実際、SAXパッケージ ( org.xml.sax ) を調べてみると、出力メソッドは1つもありません。SAXはそもそもXMLの出力ではなく、読み取りのために開発されたのです。

注:XMLFilter を使用して、入力されるXMLに手を加えることもできます。(このフィルターについては、今回のヒントの後半、および今後のヒントで詳しく説明します。)しかし、これもまたXML出力ではありません。また、SAXコールバック内でローI/Oストリームを使ってXMLを出力することも可能です。しかし、これは 上記リスト のオプション5の変形にすぎませんから、 ローI/Oストリーム の項目で説明します。

DOM

DOM (Document Object Model) はXML出力用のAPIとして、圧倒的に普及しています。DOMはXMLのインメモリー ・モデルです。つまり、それぞれの要素、属性、文字フラグメント (断片)、その他のXML構文要素をメモリー内に保管します。DOMツリーを作成するには、XML文書またはストリームを読み込んでDOMツリーにするか、またはまったく最初からDOMツリーを作成します。同様にDOMツリーを書き出すのも簡単で、ほとんどのパーサー・ソフトウェア・パッケージには、書き出し機能を持つユーティリティーが含まれています。たとえば、Apache Xercesのサンプルの1つに dom.Writer があります。これはDOMの Node を入力として、その Node のXML表記を出力します。リスト1はそのコードの一部で、出力ロジックの大部分を含んでいます。(訳注:DOMについても、書き出し機能が標準化されているわけではありません)

リスト1. DOMツリーを出力する
    public void write(Node node) {
        // is there anything to do?
        if (node == null) {
            return;
        }
        short type = node.getNodeType();
        switch (type) {
            case Node.DOCUMENT_NODE: {
                Document document = (Document)node;
                if (!fCanonical) {
                    fOut.println("<?xml version=
				\
				"1.0
				\
				" encoding=
				\
				"UTF-8
				\
				"?>");                     fOut.flush();
                    write(document.getDoctype());
                }
                write(document.getDocumentElement());
                break;
            }
            case Node.DOCUMENT_TYPE_NODE: {
                DocumentType doctype = (DocumentType)node;
                fOut.print("<!DOCTYPE ");
                fOut.print(doctype.getName());
                String publicId = doctype.getPublicId();
                String systemId = doctype.getSystemId();
                if (publicId != null) {
                    fOut.print(" PUBLIC '");
                    fOut.print(publicId);
                    fOut.print("' '");
                    fOut.print(systemId);
                    fOut.print
				\
				''); } else { fOut.print(" SYSTEM '");
				fOut.print(systemId); fOut.print('
				\
				''); } 
				}
                String internalSubset = doctype.getInternalSubset();
                if (internalSubset != null) {
                    fOut.println(" [");
                    fOut.print(internalSubset);
                    fOut.print(']');
                }
                fOut.println('>');
                break;
            }
            case Node.ELEMENT_NODE: {
                fOut.print('<');
                fOut.print(node.getNodeName());
                Attr attrs[] = sortAttributes(node.getAttributes());
                for (int i = 0; i < attrs.length; i++) {
                    Attr attr = attrs[i];
                    fOut.print(' ');
                    fOut.print(attr.getNodeName());
                    fOut.print("=
				\
				"");           normalizeAndPrint(attr.getNodeValue());
                    fOut.print('"');
                }
                fOut.print('>');
                fOut.flush();
                Node child = node.getFirstChild();
                while (child != null) {
                    write(child);
                    child = child.getNextSibling();
                }
                break;
            }
            case Node.ENTITY_REFERENCE_NODE: {
                if (fCanonical) {
                    Node child = node.getFirstChild();
                    while (child != null) {
                        write(child);
                        child = child.getNextSibling();
                    }
                }
                else {
                    fOut.print('&');
                    fOut.print(node.getNodeName());
                    fOut.print(';');
                    fOut.flush();
                }
                break;
            }
            case Node.TEXT_NODE: {
                normalizeAndPrint(node.getNodeValue());
                fOut.flush();
                break;
            }
            case Node.PROCESSING_INSTRUCTION_NODE: {
                fOut.print("<?");
                fOut.print(node.getNodeName());
                String data = node.getNodeValue();
                if (data != null && data.length() > 0) {
                    fOut.print(' ');
                    fOut.print(data);
                }
                fOut.println("?>");
                fOut.flush();
                break;
            }
        }
        if (type == Node.ELEMENT_NODE) {
            fOut.print("</");
            fOut.print(node.getNodeName());
            fOut.print('>');
            fOut.flush();
        }
    }

このコードについてはあまり詳しく説明しませんが、文書内のノードが1つずつ繰り返し処理され、すべてのノードがメモリー内に保管されることに注意してください。したがって、1,000個、2,000個、あるいは10,000個のノードを含むDOMツリーの場合、この出力コードにたどり着く ことさえ不可能でしょう。DOMツリーが構築される前に、メモリー不足エラーが発生するに違いありません。1,000個のノードを保管しようとするとメモリーが大量に消費され、ほとんどのマシンはハングアップするでしょう。また、ほとんどのデータは2つ、3つ、あるいはそれ以上のノードを必要とすることにも注意してください。各要素が1つのノードであり、その要素の中のデータもまた1つのノードです。しかも、それぞれの属性ごとにノードが追加されます。したがって、10,000個のデータからなる文書の中には、そのデータを表す20,000個、30,000個、または50,000個以上のノードが存在するかもしれません。言うまでもなく、DOMはこれほど大量のデータを処理できません。ましてや、データをファイルに出力することなど問題外です。

JAXP

JAXP (Java API for XML Processing) もまた、誤解されやすい手法です。JAXPそのものは構文解析用のAPIではなく、SAXやDOMの上にコンビニエンス層を追加するためのラッパーAPIにすぎません。したがって、基礎となるこれらのAPIがJAXPの動作を制御します。言い換えると、JAXPを介してSAXを使用する場合には、SAX自体の持つ「書き出し不能」の問題がやはり存在します。JAXPを介してDOMを使用する場合には、DOMのメモリー消費の問題がやはり発生します。JAXPは、何か新しいオプションを提供してくれるものではありません。

インメモリー・モデル

最近、DOMやSAX以外にもいつかのAPIが普及してきました。とくに、 JDOMdom4j が注目されています。他にもさまざまな手法がありますが、これらはすべて、何らかの種類のインメモリー・モデルを使用するという点で共通しています。DOMインプリメンテーションほどメモリーに負担をかけない手法もたくさんありますが、依然として、XMLツリーの各部分ごとに何らかのデータをメモリーに保管します。したがって、DOMと同じ問題、つまりメモリー・オーバーフローが遅かれ早かれ発生するでしょう。このようなAPIを使えばメモリー消費が少しは抑制されるかもしれませんが、究極的には、ハードウェアの制限のためにデータのロードや書き出しが不可能になるでしょう。

ローI/Oストリーム

最後のオプションはローI/Oストリーム (生の入出力ストリーム) です。Javaコードの場合、 java.io.OutputStream または java.io.Writer を使用して、XMLに準拠する文字を吐き出すことができます。たとえば リスト1 には、XML文字を出力ストリームに直接書き出すステートメントがいくつか含まれています。この手法はXML全体をメモリーに保管する必要がないという点で実用的ではありますが、この手法にも独特の問題点があります。まず、ロー・ストリームを扱うということは、アポストロフィや引用符などのエスケープ文字によく注意しなければなりません。出力の見栄えが悪くなったり、エラーの原因となることがよくあります。次に、開発者はツリー自体の形式をよく知っていなければなりません。要素、副要素、属性といった構造を処理するのは不可能ですから、こうした詳細を開発者が自分で把握しておく必要があります。このため、エラーの可能性がますます高くなります。言い換えると、I/Oストリームはオプションの1つではありますが、あまり便利ではありません。

では、どうするべきか

このように、膨大な作業やエラー・チェックを必要とせずに巨大なXMLデータ・セットを処理し、データを出力するためのオプションがいかに乏しいか、実際にコードを見なくてもおわかりいただけるでしょう。このヒント・シリーズでは、これらとはまったく別の1つのオプションを提示します。これは、SAXをある方法で拡張してフィルター操作と出力を可能にする方法です。今後の5つのヒントを読んだ読者は、メモリー・リソースにまったく負担をかけることなく、巨大なXML文書をいとも簡単に処理できるようになるでしょう。

次回のヒントを待っている間、読者自身のアプリケーション・コードの中にも、上記のようなオプションを使っているものが見つかるかもしれません。そのコードを分析して、いかに大量のメモリーを使っているか確認してください。私が今後のヒントでご紹介するいくつかの新しい技法を、従来のコードと比べてみることができるでしょう。次回のヒントを首を長くしてお待ちください。


ダウンロード可能なリソース


関連トピック

  • XML仕様に関するありとあらゆる詳細を、オンラインの W3Cサイト でご覧ください。
  • XML.comの " The Annotated XML Specification " (解説付きXML仕様) をご覧ください。
  • DOMについての詳細は、 DOM Level 2 Core 仕様、およびdeveloperWorks のチュートリアル " Understanding DOM " (2001年8月) をご覧ください。
  • SAXに関する詳細は、 SAXプロジェクトのホーム・ページ 、およびdeveloperWorks のチュートリアル " Understanding SAX " (2001年9月) をご覧ください。
  • スキルアップのために、" Java and XML " (Brett McLaughlin著、O'Reilly and Associates) をお読みください。
  • developerWorks の XMLゾーン には、XMLに関するさらに多くの記事があります。
  • IBM WebSphere Studio ( 日本語サイト ) は、Javaと他の言語の両方でXML開発を自動化するツールのスイートを提供します。 WebSphere Application Server と密接に統合されていますが、他のJ2EEサーバーと一緒に使用することもできます。
  • XMLおよび関連テクノロジーに関するIBM認定デベロッパー になる方法をご覧ください。
  • この記事のようなXMLに関して役立つヒントを毎週受け取るのはいかがですか?developerWorksの XMLヒントのニュースレター の購読を登録してください。
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=242473
ArticleTitle=ヒント: 巨大なXML文書を出力する: 第1 回
publish-date=03012003