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

时间:2011-09-22 21:36:37

标签: java xml xml-parsing sax stax

像SAX和StAX这样的流式xml解析器比构建像DOM解析器这样的树结构的解析器更快,内存效率更高。 SAX是一个推送解析器,意味着它是观察者模式的一个实例(也称为监听器模式)。 SAX首先出现了,但随后出现了StAX - 一个拉解析器,意味着它基本上就像迭代器一样。

你可以找到为什么在各处都喜欢StAX而不是SAX的理由,但它通常归结为:“它更容易使用”。

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

这一切让我想知道:有没有理由选择SAX而不是StAX?

6 个答案:

答案 0 :(得分:77)

概述
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 events ,由您决定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”开始元素,您需要确定您实际正在解析一个地址,然后将XML事件路由到其作业来解释地址。

的StAX
StAX'拉出'XML events ,由您决定在程序/数据中接收XML事件的位置。

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

当然,您总是希望在方法中接收“建筑”事件,该方法的作用是解释地址。

讨论
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;
}

这很简单,没有误解的余地。只记得正确递减水平:

一个。在您预期的字符之后但在某个标签中有一个END_ELEMENT应该包含字符(在上面的模式中):

<Name>Thomas</Name>

<Name></Name>

对于缺失的子树也是如此,你明白了。

B中。在调用在start元素上调用的subparsing方法之后,在相应的end元素之后返回,即解析器的位置低于方法调用之前的一个级别(上面的模式)。

注意这种方法如何完全忽略'可忽略的'空白,以实现更强大的实现。

解析器
对于大多数功能,请使用Woodstox,或者使用Aaalto-xml获取速度。

答案 1 :(得分:18)

要概括一点,认为StAX可以与SAX一样高效。使用StAX 的改进设计,我无法真正找到首选SAX解析的情况,除非使用遗留代码。

编辑:根据此博客Java SAX vs. StAX StAX不提供架构验证。

答案 2 :(得分:15)

@Rinke:我想只有当你不需要处理/处理XML内容时,我才会想到SAX优于STAX;例如你要做的唯一事情是检查传入XML的格式是否正确并且只想处理错误...在这种情况下你可以简单地在SAX解析器上调用parse()方法并指定错误处理程序来处理任何解析问题....所以基本上STAX绝对是你想要处理内容的场景中的首选,因为SAX内容处理程序太难编码......

这种情况的一个实际示例可能是,如果您的企业系统中有一系列SOAP节点,并且入口级SOAP节点只允许那些SOAP XML通过下一阶段的格式良好,那么我没有看到任何我之所以会使用STAX。我只想使用SAX。

答案 3 :(得分:1)

这都是平衡。

你可以使用阻塞队列和一些线程技巧将一个SAX解析器变成一个拉解析器,所以对我来说,差异远小于最初的差异。

我认为目前StAX需要通过第三方jar打包,而SAX在javax中免费提供。

我最近选择了SAX并在其周围构建了一个拉解析器,所以我不需要依赖第三方jar。

Java的未来版本几乎肯定会包含StAX实现,因此问题就会消失。

答案 4 :(得分:0)

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

您可以在Java StAX Tutorials

中详细了解StAX

答案 5 :(得分:-1)

这些答案提供的大部分信息都有些过时......在2013年的研究论文中对所有XML解析库进行了全面的研究...阅读它,你会很容易看到明显的赢家(提示:那里只是一个真正的赢家)...

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