[英]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.