什么时候应该选择SAX而不是StAX?


81

与构建像DOM解析器之类的树结构的解析器一样,流式XML解析器(例如SAX和StAX)更快,内存效率更高。SAX是推式解析器,这意味着它是观察者模式(也称为侦听器模式)的实例。SAX首先出现,然后是StAX-拉式解析器,这意味着它基本上像迭代器一样工作。

您可以找到在任何地方都偏爱StAX而不是SAX的原因,但是通常可以归结为:“更易于使用”。

在JAXP上的Java教程中,StAX被模糊地呈现为DOM和SAX之间的中间部分:“它比SAX更容易,并且比DOM更有效”。但是,我从来没有发现StAX比SAX慢或低内存效率的任何线索。

这一切使我感到奇怪:是否有任何理由选择SAX而不是StAX?

Answers:



81

概述
XML文档是层次结构的文档,其中相同的元素名称和名称空间可能出现在多个地方,具有不同的含义,并且具有不确定的深度(递归)。通常,解决大问题的方法是将它们分为小问题。在XML解析的上下文中,这意味着使用特定于XML的方法来解析XML的特定部分。例如,一种逻辑将解析一个地址:

<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”开始元素,则需要确定您实际上是在解析一个Address,然后将XML事件路由到用于解释Address的方法。

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可以给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.在调用子解析方法之后,这些方法在开始元素上被调用,并在相应的结束元素之后返回,即解析器比方法调用之前低一级(上述模式)。

请注意,这种方法也是如何完全忽略“可忽略的”空白,以实现更可靠的实现。

解析器
使用Woodstox来实现大多数功能,而使用Aaalto-xml来提高速度。


在您的开场白中,其内容为“ ...而SAX中...”。这是错字吗?(用“ SAX”代替“ StAX”)在任何情况下,谢谢您的回答。如果我对您的理解是正确的,那么您是说与在StAX方法中跟踪xml树位置的需求相比,SAX方法中的隐式状态是一种好处。
Rinke

感谢您的回答(现在更加详尽)。恐怕我仍然看不到使用SAX代替StAX的充分理由。您的答案很好地说明了两个处理器如何工作。
Rinke

对于简单的文档,它们是相同的。例如,查看以下模式:mpeg.chiariglione.org/technologies/mpeg-21/mp21-did/index.htm和StAX将更加实用。
ThomasRS 2011年

简而言之,因为您已经在编写代码,所以您了解要解析的文档的哪一部分,即,将SAX事件映射到正确的代码的所有逻辑都被浪费了。
ThomasRS 2011年

16

@Rinke:我想只有在我想使用SAX而不是STAX的时候,以防万一您不需要处理/处理XML内容。例如,您唯一想做的就是检查传入XML的格式是否正确,并且只想处理错误(如果有)...在这种情况下,您只需在SAX解析器上调用parse()方法并指定错误处理程序即可处理解析问题。...因此基本上,在您要处理内容的情况下,STAX绝对是首选,因为SAX内容处理程序很难编写代码...

这种情况的一个实际示例可能是,如果您的企业系统中有一系列SOAP节点,而入门级SOAP节点仅允许格式良好的SOAP XML通过下一阶段,那么我看不出任何理由将使用STAX。我只会使用SAX。


我选择此答案是迄今为止最好的答案。尽管这是一个很好的答案,但是我认为它不是100%权威且明确的。欢迎新的答案。
Rinke

1

全部平衡。

您可以使用阻塞队列和一些线程欺骗将SAX解析器转换为拉式解析器,因此,对我而言,与最初看起来的区别要小得多。

我相信当前StAX需要通过第三方jar打包,而SAX在Javax中是免费的。

我最近选择了SAX并围绕它构建了一个拉式解析器,因此我不需要依赖第三方jar。

Java的未来版本几乎可以肯定会包含StAX实现,因此问题消除了。


1
Java SE 6确实包含StAX。但是例如android实现不包含它。
BjarneBoström,2015年

0

StAX使您可以快速创建双向XML解析器。在性能和可用性方面,它被证明是其他方法(例如DOM和SAX)的更好替代方案

您可以在Java StAX教程中阅读有关StAX的更多信息。


-1

这些答案提供的大多数信息有些过时了...在此2013年的研究论文中已经对所有XML解析库进行了全面研究...阅读它,您将很容易看到明显的赢家(提示:只有一个真正的赢家)...

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


1
我读了这篇论文,获胜者是使用的光标API的StAX XMLStreamReader
罗兰

非常有趣:),您的意思是乌龟竞赛的获胜者:)
vtd-xml-author

我只是重新阅读了这篇论文,是的,StaX优于vtd,速度更快,内存消耗更少。那么你的观点是什么?
罗兰

赢家是stAX以什么方式?您指的是论文的哪一部分?修改文档,还是选择或区分?显然,本文的作者得出了不同的结论。但它们可能完全错了……
vtd-xml-author

1
例如第80页:根据结果(图11和图12),我们可以看到StAX是性能更好的API,其次是VTD。但是,VTD占用大量内存。内存消耗可能是功能受限的环境的瓶颈。
罗兰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.