web-dev-qa-db-ja.com

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

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

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

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

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

79
Rinke

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

[〜#〜] edit [〜#〜]:このブログによると Java SAX vs. StAXStAXofferスキーマ検証なし。

21
Johan Sjöberg

概要
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は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」開始要素の場合、実際にAddressを解析していることを確認してから、XMLイベントをAddressを解釈するジョブのメソッドにルーティングする必要があります。

StAX
StAX 'pulls' XML events、プログラム/データのどこを決定するかはあなた次第XMLイベントを受信します。

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

もちろん、アドレスを解釈することを目的とするメソッドで、常に「Building」イベントを受け取りたいでしょう。

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

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

したがって、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 で移動します。

80
ThomasRS

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

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

16
ag112

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

ブロッキングキューとスレッドトリックを使用して、SAXパーサーをプルパーサーに変えることができます。

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

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

Javaの将来のバージョンには、ほぼ確実にStAX実装が含まれるので、問題はなくなります。

1
OldCurmudgeon

StAXを使用すると、高速の双方向XMLパーサーを作成できます。パフォーマンスと使いやすさの両方の点で、DOMやSAXなどの他の方法よりも優れた代替手段であることが証明されています。

StAXの詳細については、 Java StAXチュートリアル をご覧ください。

0