繁体   English   中英

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

[英]When should I choose SAX over StAX?

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

你可以找到为什么在任何地方都喜欢StAX而不是SAX的原因,但它通常归结为:“它更容易使用”。

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

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

概观
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
}

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

讨论
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获取速度。

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

编辑 :根据这篇博客, Java SAX vs. StAX StAX提供架构验证。

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

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

这都是平衡。

您可以使用阻塞队列和一些线程技巧将SAX解析器转换为拉解析器,因此,对我而言,与最初看起来相比,差异要小得多。

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM