像SAX和StAX这样的流式xml解析器比构建像DOM解析器这样的树结构的解析器更快,内存效率更高。 SAX是一个推送解析器,意味着它是观察者模式的一个实例(也称为监听器模式)。 SAX首先出现了,但随后出现了StAX - 一个拉解析器,意味着它基本上就像迭代器一样。
你可以找到为什么在各处都喜欢StAX而不是SAX的理由,但它通常归结为:“它更容易使用”。
在JAXP的Java教程中,StAX被模糊地呈现为DOM和SAX之间的中间:“它比SAX更容易,比DOM更高效”。但是,我从来没有发现任何线索,StAX比SAX更慢或内存效率更低。
这一切让我想知道:有没有理由选择SAX而不是StAX?
答案 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
中详细了解StAX答案 5 :(得分:-1)
这些答案提供的大部分信息都有些过时......在2013年的研究论文中对所有XML解析库进行了全面的研究...阅读它,你会很容易看到明显的赢家(提示:那里只是一个真正的赢家)...
http://recipp.ipp.pt/bitstream/10400.22/1847/1/ART_BrunoOliveira_2013.pdf