StAXではなくSAXを選択する必要があるのはいつですか?


81

SAXやStAXのようなストリーミングxmlパーサーは、DOMパーサーのようなツリー構造を構築するパーサーよりも高速でメモリ効率が高くなります。SAXはプッシュパーサーです。つまり、オブザーバーパターン(リスナーパターンとも呼ばれます)のインスタンスです。SAXが最初にありましたが、次にStAX(プルパーサー)が登場しました。これは、基本的にイテレーターのように機能することを意味します。

どこでもSAXよりもStAXを好む理由を見つけることができますが、それは通常、「使いやすい」ということになります。

JAXPに関するJavaチュートリアルでは、StAXはDOMとSAXの中間として漠然と示されています。「SAXよりも簡単で、DOMよりも効率的です」。ただし、StAXがSAXよりも低速またはメモリ効率が低いという手がかりは見つかりませんでした。

このすべてが私に不思議に思いました:StAXの代わりにSAXを選ぶ理由はありますか?

回答:


22

少し一般化すると、StAXと同じくらい効率的だと思いますSAX。の改善された設計でStAXSAX、レガシーコードを使用しない限り、解析が好まれる状況を実際に見つけることはできません。

編集:このブログによると、JavaSAXとStAX StAXはスキーマ検証を提供していません。


2
スタックスの上に検証を追加することはそれほど難しくありません。先日、自分で実装しました。
jtahlborn 2011


81

概要
XMLドキュメントは階層ドキュメントであり、同じ要素名と名前空間が複数の場所で発生し、意味が異なり、深さが無限に(再帰的に)発生する可能性があります。通常のように、大きな問題の解決策は、それらを小さな問題に分割することです。XML解析のコンテキストでは、これは、そのXMLに固有のメソッドでXMLの特定の部分を解析することを意味します。たとえば、1つのロジックがアドレスを解析します。

<Address>
    <Street>Odins vei</Street>    
    <Building>4</Building>
    <Door>b</Door>
</Address>

つまり、あなたは方法を持っているでしょう

AddressType parseAddress(...); // A

または

void parseAddress(...); // B

ロジックのどこかで、XML入力引数を取り、オブジェクトを返します(Bの結果は後でフィールドからフェッチできます)。

SAX
SAXはXMLイベントを「プッシュ」し、XMLイベントがプログラム/データのどこに属するかを決定するのはあなたに任されています。

// method in stock SAX handler
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
    // .. your logic here for start element
}

'Building' start要素の場合、実際にAddressを解析していることを確認してから、Addressを解釈するジョブを持つメソッドにXMLイベントをルーティングする必要があります。

StAX
StAXはXMLイベントを「プル」し、プログラム/データのどこでXMLイベントを受信するかを決定するのはあなたに任されています。

// method in standard StAX reader
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
    // .. your logic here for start element
}

もちろん、Addressを解釈するジョブを持つメソッドで「Building」イベントを常に受信する必要があります。

考察
SAXとStAXの違いは、プッシュとプルの違いです。どちらの場合も、解析状態は何らかの方法で処理する必要があります。

これは、SAXでは一般的な方法B、StAXでは方法Aに変換されます。さらに、SAXはB個の個別のXMLイベントを提供する必要がありますが、StAXは(XMLStreamReaderインスタンスを渡すことによって)A個の複数のイベントを提供できます。

したがって、Bは最初に解析の前の状態をチェックし、次に個々のXMLイベントを処理してから、状態を(フィールドに)格納します。方法Aは、満足するまでXMLStreamReaderに複数回アクセスすることにより、XMLイベントを一度に処理できます。

結論
StAXを使用すると、XML構造に従って解析(データバインディング)コードを構造化できます。したがって、SAXに関連して、「状態」はStAXのプログラムフローから暗黙的に示されますが、SAXでは、ほとんどのイベント呼び出しで、常に何らかの状態変数を保持し、その状態に従ってフローをルーティングする必要があります。

最も単純なドキュメントを除くすべてのドキュメントにStAXをお勧めします。後で最適化としてSAXに移行します(ただし、それまでにバイナリに移行することをお勧めします)。

StAXを使用して解析する場合は、次のパターンに従ってください。

public MyDataBindingObject parse(..) { // provide input stream, reader, etc

        // set up parser
        // read the root tag to get to level 1
        XMLStreamReader reader = ....;

        do {
            int event = reader.next();
            if(event == XMLStreamConstants.START_ELEMENT) {
              // check if correct root tag
              break;
            }

            // add check for document end if you want to

        } while(reader.hasNext());

        MyDataBindingObject object = new MyDataBindingObject();
        // read root attributes if any

        int level = 1; // we are at level 1, since we have read the document header

        do {
            int event = reader.next();
            if(event == XMLStreamConstants.START_ELEMENT) {
                level++;
                // do stateful stuff here

                // for child logic:
                if(reader.getLocalName().equals("Whatever1")) {
                    WhateverObject child = parseSubTreeForWhatever(reader);
                    level --; // read from level 1 to 0 in submethod.

                    // do something with the result of subtree
                    object.setWhatever(child);
                }

                // alternatively, faster
                if(level == 2) {
                    parseSubTreeForWhateverAtRelativeLevel2(reader);
                    level --; // read from level 1 to 0 in submethod.

                    // do something with the result of subtree
                    object.setWhatever(child);
                }


            } else if(event == XMLStreamConstants.END_ELEMENT) {
                level--;
                // do stateful stuff here, too
            }

        } while(level > 0);

        return object;
}

したがって、サブメソッドはほぼ同じアプローチ、つまりカウントレベルを使用します。

private MySubTreeObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {

    MySubTreeObject object = new MySubTreeObject();
    // read element attributes if any

    int level = 1;
    do {
        int event = reader.next();
        if(event == XMLStreamConstants.START_ELEMENT) {
            level++;
            // do stateful stuff here

            // for child logic:
            if(reader.getLocalName().equals("Whatever2")) {
                MyWhateverObject child = parseMySubelementTree(reader);
                level --; // read from level 1 to 0 in submethod.

                // use subtree object somehow
                object.setWhatever(child);
            }

            // alternatively, faster, but less strict
            if(level == 2) {
              MyWhateverObject child = parseMySubelementTree(reader);
                level --; // read from level 1 to 0 in submethod.

                // use subtree object somehow
                object.setWhatever(child);
            }


        } else if(event == XMLStreamConstants.END_ELEMENT) {
            level--;
            // do stateful stuff here, too
        }

    } while(level > 0);

    return object;
}

そして最終的には、基本タイプを読み取るレベルに到達します。

private MySetterGetterObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {

    MySetterGetterObject myObject = new MySetterGetterObject();
    // read element attributes if any

    int level = 1;
    do {
        int event = reader.next();
        if(event == XMLStreamConstants.START_ELEMENT) {
            level++;

            // assume <FirstName>Thomas</FirstName>:
            if(reader.getLocalName().equals("FirstName")) {
               // read tag contents
               String text = reader.getElementText()
               if(text.length() > 0) {
                    myObject.setName(text)
               }
               level--;

            } else if(reader.getLocalName().equals("LastName")) {
               // etc ..
            } 


        } else if(event == XMLStreamConstants.END_ELEMENT) {
            level--;
            // do stateful stuff here, too
        }

    } while(level > 0);

    // verify that all required fields in myObject are present

    return myObject;
}

これは非常に簡単で、誤解の余地はありません。レベルを正しくデクリメントすることを忘れないでください:

A.文字を期待したが、文字を含む必要があるタグにEND_ELEMENTを取得した後(上記のパターン):

<Name>Thomas</Name>

代わりに

<Name></Name>

欠落しているサブツリーについても同じことが言えます。

B.開始要素で呼び出されるサブ解析メソッドを呼び出した後、対応する終了要素の後に戻ります。つまり、パーサーはメソッド呼び出しの前よりも1レベル低くなります(上記のパターン)。

より堅牢な実装のために、このアプローチが「無視できる」空白も完全に無視することに注意してください。

パーサーは、ほとんどの機能について
Woodstoxを使用し、速度についてはAaalto-xmlを使用します。


冒頭陳述では、「...一方、SAXでは...」と書かれています。これはタイプミスですか?(「StAX」ではなく「SAX」)とにかく答えてくれてありがとう。私があなたを正しく理解しているなら、SAXアプローチの暗黙の状態は、StAXアプローチでxmlツリーの場所を追跡する必要性と比較して利点であると言っています。
リンケ2011

(今ではさらに手の込んだ)答えをありがとう。StAXの代わりにSAXを使用する正当な理由がまだわからないのではないかと思います。あなたの答えは、両方のプロセッサがどのように機能するかについての良い説明です。
リンケ2011

単純なドキュメントの場合、それらは同じです。たとえば、次のスキーマを見てください:mpeg.chiariglione.org/technologies/mpeg-21/mp21-did/index.htmそしてStAXはより実用的です。
ThomasRS 2011

一言で言えば、すでにコードを書いているので、解析しているドキュメントのどの部分、つまりSAXイベントを正しいコードにマップするためのすべてのロジックが無駄になっているのかを理解できます。
ThomasRS 2011

16

@Rinke:XMLコンテンツを処理/処理する必要がない場合に備えて、STAXよりもSAXを優先することを考えるのは時間だけだと思います。たとえば、受信XMLの整形式性をチェックし、エラーが発生した場合はそれを処理するだけです...この場合、SAXパーサーでparse()メソッドを呼び出し、エラーハンドラーを指定して任意のXMLを処理できます。構文解析の問題....したがって、SAXコンテンツハンドラーのコーディングが難しすぎるため、コンテンツを処理するシナリオでは、基本的にSTAXが間違いなく好ましい選択です...

このケースの実用的な例の1つは、エンタープライズシステムに一連のSOAPノードがあり、エントリレベルのSOAPノードがそれらのSOAP XMLを整形式である次のステージにのみ通過させる場合、理由がわかりません。 STAXを使用します。SAXを使うだけです。


私はこの答えをこれまでのところ最良のものとして選びました。それは良い答えですが、100%信頼できる明確なものではないと思います。新しい答えは大歓迎です。
リンケ2011年

1

それはすべてバランスです。

ブロッキングキューといくつかのスレッドトリックを使用して、SAXパーサーをプルパーサーに変えることができるので、私には、最初に思われるよりもはるかに少ない違いがあります。

現在、SAXはjavaxで無料で提供されていますが、StAXはサードパーティのjarを介してパッケージ化する必要があると思います。

私は最近SAXを選択し、その周りにプルパーサーを構築したので、サードパーティのjarに依存する必要はありませんでした。

Javaの将来のバージョンには、ほぼ確実にStAX実装が含まれるため、問題は解消されます。


1
Java SE6にはStAXが含まれています。しかし、例えば、Androidの実装にはそれが含まれていません。
Bjarneボストロム


-1

これらの回答によって提供される情報のほとんどはやや時代遅れです...この2013年の研究論文ではすべてのXML解析ライブラリの包括的な研究がありました...それを読むと、明確な勝者が簡単にわかります(ヒント:1つだけです)真の勝者)...

http://recipp.ipp.pt/bitstream/10400.22/1847/1/ART_BrunoOliveira_2013.pdf


1
私は論文を読みました、勝者はのようにカーソルAPIを使用するStAXXMLStreamReaderです。
ローランド

非常に面白い:)、あなたは亀のレースの勝者を意味します:)
vtd-xml-author 2016

論文を読み直したところ、そうです、StaXはvtdよりも優れており、より高速でメモリ消費量が少なくなっています。それで、あなたのポイントは何ですか?
ローランド

勝者はどのようにstAXですか?論文のどの部分を参照していますか?ドキュメントの変更、または選択または差別化?どうやら論文の著者は別の結論を引き出したようです。しかし、それらは完全に間違っている可能性があります...
vtd-xml-author 2016

1
例:80ページ:結果(図11および図12)によると、StAXがパフォーマンスの優れたAPIであり、VTDがそれに続くことがわかります。ただし、VTDはかなりの量のメモリを消費します。メモリ消費は、機能が制限されている環境のボトルネックになる可能性があります。
ローランド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.