【问题标题】:When should I choose SAX over StAX?我什么时候应该选择 SAX 而不是 StAX?
【发布时间】:2011-11-23 05:20:03
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: java xml xml-parsing sax stax


    【解决方案1】:

    概述
    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 事件在您的程序/数据中的位置。 p>

    // method in stock SAX handler
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
        // .. your logic here for start element
    }
    

    如果是“建筑”开始元素,您需要确定您实际上是在解析地址,然后将 XML 事件路由到负责解释地址的方法。

    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
    }
    

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

    讨论
    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-tree 位置的需要相比,SAX 方法中的隐式状态是一个好处。
    • 感谢(现在更加详尽)的回答。恐怕我仍然看不出使用 SAX 而不是 StAX 的充分理由。您的回答很好地解释了两个处理器的工作原理。
    • 对于简单的文档,它们是相同的。例如看一下这个架构:mpeg.chiariglione.org/technologies/mpeg-21/mp21-did/index.htm,StAX 会更实用。
    【解决方案2】:

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

    编辑:根据这个博客Java SAX vs. StAXStAXoffer 没有模式验证。

    【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 我选择了这个答案作为迄今为止最好的答案。尽管这是一个很好的答案,但我不觉得它是 100% 权威和清晰的。欢迎新的答案。
    【解决方案4】:

    这都是一种平衡。

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

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

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

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

    【讨论】:

    • Java SE 6 确实包含 StAX。但是例如android实现不包括它。
    【解决方案5】:

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

    你可以在Java StAX Tutorials阅读更多关于StAX的信息

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 我读了这篇论文,获胜者是 StAX,它使用 XMLStreamReader 中的游标 API。
      • 很有趣:),你的意思是乌龟比赛的获胜者:)
      • 我刚刚重读了这篇论文,是的,StaX 优于 vtd,速度更快,内存消耗更少。那你的意思是什么?
      • 赢家是 stAX 以什么方式?你指的是论文的哪一部分?修改文档,还是选择或区分?显然,该论文的作者得出了不同的结论。但他们可能完全错了......
      • 例如第 80 页:根据结果(图 11 和图 12)我们可以看到 StAX 是性能更好的 API,其次是 VTD。但是,VTD 会消耗大量内存。对于提供有限功能的环境来说,内存消耗可能是一个瓶颈。
      猜你喜欢
      • 1970-01-01
      • 2015-12-12
      • 1970-01-01
      • 1970-01-01
      • 2016-08-09
      • 2011-12-26
      • 2017-09-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多