83

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

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

在有关 JAXP 的 Java 教程中,StAX 模糊地表示为 DOM 和 SAX 之间的中间:“它比 SAX 更容易,比 DOM 更高效”。但是,我从未发现任何迹象表明 StAX 会比 SAX 更慢或内存效率更低。

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

4

6 回答 6

84

概述
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”开始元素,您需要确定您实际上是在解析地址,然后将 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
}

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

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

于 2011-09-23T01:51:19.223 回答
22

概括一下,我认为StAX可以像SAX. 随着设计的改进,StAX我真的找不到任何SAX首选解析的情况,除非使用遗留代码。

编辑:根据此博客,Java SAX 与 StAX StAX不提供模式验证。

于 2011-09-22T21:48:35.087 回答
16

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

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

于 2011-10-06T07:41:46.097 回答
1

这都是一种平衡。

您可以使用阻塞队列和一些线程技巧将 SAX 解析器变成拉式解析器,因此,对我来说,与最初看起来的区别要小得多。

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

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

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

于 2011-10-10T13:49:15.810 回答
0

StAX 使您能够创建快速的双向 XML 解析器。在性能和可用性方面,它证明了比 DOM 和 SAX 等其他方法更好的选择

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

于 2015-04-01T09:59:05.883 回答
-1

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

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

于 2016-04-19T20:26:25.973 回答