博文引自:http://zangweiren.iteye.com/blog/647334java
Java 6.0对XML支持的新特性有许多方面。好比StAX、针对XML-Web服务的Java架构(JAX-WS)2.0、针对XML绑定的API(JAXB)2.0、XML数字签名API,甚至还支持SQL:2003 'XML'数据类型。在这一篇文章中咱们将要介绍的是StAX技术,由于它在咱们的开发中将被使用地更加频繁。
StAX是Streaming API for XML的缩写,是一种针对XML的流式拉分析API。关于对XML进行分析(或解析)的技术,你们必定都不陌生了。在Java 6.0以前,就已经有四种: 网络
关于它们的解析原理,以及性能和优缺点,我会在本文的结尾作一个简要的介绍。这篇文章中,咱们主要说说StAX这种新的解析方式。
首先咱们来搞清楚两个概念:推分析和拉分析。
在程序中访问和操做XML文件通常有两种模型:DOM(文档对象模型)和流模型。它们的优缺点以下:
架构
关于什么是DOM,文章结尾处会有介绍。这里咱们简单说一下流:它是一个连续的字节序列,能够理解为不停地从源头向目标搬运着字节的特殊对象。
让咱们回到主题。流模型每次迭代XML文档中的一个节点,适合于处理较大的文档,所耗内存空间小。它有两种变体--“推”模型和“拉”模型。
框架
到此,咱们就弄明白了“推分析”和“拉分析”的概念:
ide
StAX就是一种拉分析式的XML解析技术。它也支持对XML文件的生成操做,可是这篇文章里咱们只介绍有关解析的知识。
从一开始,JAXP(Java API for XML Processing)就提供了两种方法来处理XML:DOM和SAX。StAX是一种面向流的新方法,最终版本于2004年3月发布,并成为JAXP 1.4(包含在Java 6.0中)的一部分。StAX的实现使用了JWSDP(Java Web Services Development Pack)1.6,并结合了SJSXP(Sun Java System XML Streaming Parser,位于javax.xml.stream.*包中)。
JWSDP是用来开发Web Services、Web应用程序以及Java应用(主要是XML处理)的开发包。它包含的Java API有: 模块化
JWSDP的早期版本中还包括: 性能
如今,JWSDP已经被GlassFish所替代。
StAX包括两套处理XML的API,分别提供了不一样程度的抽象。它们是:基于指针的API和基于迭代器的API。
咱们先来了解基于指针的API。它把XML做为一个标记(或事件)流来处理,应用程序能够检查解析器的状态,得到解析的上一个标记的信息,而后再处理下一个标记,依次类推。
在开始API探索以前,咱们首先建立一个名为users.xml的XML文档用于测试,它的内容以下:
测试
<?xml version="1.0" encoding="UTF-8"?> <company> <depart title="Develop Group"> <user name="Tom" age="28" gender="male" >Manager</user> <user name="Lily" age="26" gender="female" /> </depart> <depart title="Test Group"> <user name="Frank" age="32" gender="male" >Team Leader</user> <user name="Bob" age="45" gender="male" /> <user name="Kate" age="25" gender="female" /> </depart> </company>
可让咱们使用基于指针的API的接口是javax.xml.stream.XMLStreamReader(很遗憾,你不能直接实例化它),要获得它的实例,咱们须要借助于javax.xml.stream.XMLInputFactory类。根据JAXP的传统风格,这里使用了抽象工厂(Abstract Factory)模式。若是你对这个模式很熟悉的话,就可以在脑海中想象出咱们将要编写的代码的大体框架了。
首先,得到一个XMLInputFactory的实例。方法是:
this
或者:
编码
这两个方法是等价的,它们都是建立了一个新的实例,甚至实例的类型都是彻底一致的。由于它们的内部实现都是:
{ return (XMLInputFactory) FactoryFinder.find("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl"); }
接下来咱们就能够建立XMLStreamReader实例了。咱们有这样一组方法能够选择:
XMLStreamReader createXMLStreamReader(java.io.Reader reader) throws XMLStreamException; XMLStreamReader createXMLStreamReader(javax.xml.tranform.Source source) throws XMLStreamException; XMLStreamReader createXMLStreamReader(java.io.InputStream stream) throws XMLStreamException; XMLStreamReader createXMLStreamReader(java.io.InputStream stream, String encoding) throws XMLStreamException; XMLStreamReader createXMLStreamReader(String systemId, java.io.InputStream stream) throws XMLStreamException; XMLStreamReader createXMLStreamReader(String systemId, java.io.Reader reader) throws XMLStreamException;
这些方法都会根据给定的流建立一个XMLStreamReader实例,你们能够依据流的类型、是否须要指定解析XML的编码或者systemId来选择相应的方法。
在这里,咱们对systemId稍做说明,并简单解释一下它与publicId的区别。
systemId和publicId是XML文档里DOCTYPE元素中常常出现的两个属性。它们都是对外部资源的引用,用以指明引用资源的地址。systemId是直接引用资源,publicId是间接定位外部资源。具体一点说是这样:
好了,咱们接着用以上列出的第一个接口来建立一个XMLStreamReader实例:
try { XMLStreamReader reader = factory.createXMLStreamReader(new FileReader("users.xml")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); }
要遍历XML文档,须要用到XMLStreamReader的下面几个方法:
int getEventType(); boolean hasNext() throws XMLStreamException; int next() throws XMLStreamException;
getEventType()方法返回XMLStreamConstants接口中定义的一个标记常量,表示当前指针所指向标记(或事件)的类型。根据当前事件类型的不一样,应用程序能够作出不一样的处理。标记常量的类型和含义以下:
START_DOCUMENT:文档的开始
END_DOCUMENT:文档的结尾
START_ELEMENT:元素的开始
END_ELEMENT:元素的结尾
PROCESSING_INSTRUCTION:处理指令
CHARACTERS:字符(文本或空格)
COMMENT:注释
SPACE:可忽略的空格
ENTITY_REFERENCE:实体的引用
ATTRIBUTE:元素的属性
DTD:DTD
CDATA:CDATA块
NAMESPACE:命名空间的声明
NOTATION_DECLARATION:标记的声明
ENTITY_DECLARATION:实体的声明
next()方法将指针移动到下一个标记,它同时返回这个标记(或事件)的类型。此时若接着调用getEventType()方法则返回相同的值。
hasNext()用于判断是否还有下一个标记。只有当它返回true时才能够调用next()以及其它移动指针的方法。
看了上面几个方法的介绍,你们就会发现使用XMLStreamReader遍历XML文档是很是容易的,由于它的用法和每一个人都熟悉的Java迭代器(Iterator)是同样的。下面咱们就用已经掌握的这几个方法对上文中给出的XML文档作一个测试。但愿你还记得它的内容,若是忘记了,请翻回去从新浏览一下。
咱们的测试代码以下:
/** * 列出全部用户 * * @author zangweiren 2010-4-17 * */ public class ListUsers { // 得到解析器 public static XMLStreamReader getStreamReader() { String xmlFile = ListUsers.class.getResource("/").getFile() + "users.xml"; XMLInputFactory factory = XMLInputFactory.newFactory(); try { XMLStreamReader reader = factory .createXMLStreamReader(new FileReader(xmlFile)); return reader; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); } return null; } // 列出全部用户名称 public static void listNames() { XMLStreamReader reader = ListUsers.getStreamReader(); // 遍历XML文档 try { while (reader.hasNext()) { int event = reader.next(); // 若是是元素的开始 if (event == XMLStreamConstants.START_ELEMENT) { // 列出全部用户名称 if ("user".equalsIgnoreCase(reader.getLocalName())) { System.out.println("Name:" + reader.getAttributeValue(null, "name")); } } } reader.close(); } catch (XMLStreamException e) { e.printStackTrace(); } } public static void main(String[] args) { ListUsers.listNames(); } }
运行结果:
在上面的示例代码中,咱们用到了XMLStreamReader的两个新方法:
String getLocalName();
String getAttributeValue(String namespaceURI, String localName);
与此相关的还有一个方法:
QName getName();
这三个方法牵扯到XML的namespace(命名空间)、localName(本地名称)、QName(Qualified Name,限定名称)三个概念,咱们顺便解释一下:
命名空间是为了支持相同名称不一样含义的XML标签而产生的,它能够这么定义:
<com:company xmlns:com="http://www.zangweiren.com/company"> <!-- here is other tags --> </com:company>
其中,com是命名空间的前缀,company是命名空间的标签,http://www.zangweiren.com/company是命名空间的标识,相同的标识被认为是同一个命名空间。标识又叫URI,是惟一的,有URL(统一资源定位器)和URN(统一资源名称)两种。前缀是命名空间的简写,目的是为了使用方便。命名空间被声明后就能够被使用:
<com:company xmlns:com="http://www.zangweiren.com/company"> <com:depart name="Develop Group" /> </com:company>
在上例的<com:depart />标签中,前缀com是命名空间,depart是localName,这两个合起来就是QName。
在明白了这三个XML基本概念以后,也就明白了getLocalName()和getAttributeValue(String namespaceURI, String localName)方法的含义。
如今,咱们已经学会了使用XMLStreamReader遍历XML文档,并对特定标签进行解析了。
咱们再来看看下面两个方法:
String getElementText() throws XMLStreamException; int nextTag() throws XMLStreamException;
getElementText()方法返回元素的开始标签(START_ELEMENT)和关闭标签(END_ELEMENT)之间的全部文本内容,若遇到嵌套的元素就会抛出异常。
nextTag()方法将跳过全部空白、注释或处理指令,直到遇到START_ELEMENT或END_ELEMENT。它在解析只含元素内容的XML文档时颇有用。不然,在发现标记以前遇到非空白文本(不包括注释和处理指令),就会抛出异常。
好比咱们修改上一个测试程序,增长一个新方法:
// 列出全部用户的名称和年龄 public static void listNamesAndAges() { XMLStreamReader reader = ListUsers.getStreamReader(); try { while (reader.hasNext()) { // 跳过全部空白、注释或处理指令,到下一个START_ELEMENT int event = reader.nextTag(); if (event == XMLStreamConstants.START_ELEMENT) { if ("user".equalsIgnoreCase(reader.getLocalName())) { System.out.println("Name:" + reader.getAttributeValue(null, "name") + ";Age:" + reader.getAttributeValue(null, "age")); } } } reader.close(); } catch (XMLStreamException e) { e.printStackTrace(); } }
而后把它添加到主方法中:
public static void main(String[] args) { ListUsers.listNames(); ListUsers.listNamesAndAges(); }
运行它试试看,在解析到<user name="Tom" age="28" gender="male" >Manager</user>的时候会报错,所以你会获得一个相似这样的错误信息:
javax.xml.stream.XMLStreamException: ParseError at [row,col]:[4,53]
Message: found: CHARACTERS, expected START_ELEMENT or END_ELEMENT
对于基于指针的XMLStreamReader来讲,虽然API文档说的是“事件”,可是咱们把它当作“标记”更易于理解,并且不会与另外一套基于事件的API相混淆。
XMLStreamReader的某些方法,不管当前标记(或事件)是什么类型的,均可以被调用。它们的定义和做用以下:
对于以上方法都很容易理解和记忆,咱们再也不编写代码展现它们的效果。
让咱们看看有关属性操做方法。仍是首先熟悉一下它们的定义:
int getAttributeCount(); String getAttributeLocalName(int index); QName getAttributeName(int index); String getAttributeNamespace(int index); String getAttributePrefix(int index); String getAttributeType(int index); String getAttributeValue(int index); String getAttributeValue(String namespaceURI, String localName);
这些方法都十分容易理解,基本上看方法的名称和参数就知道它的用途了。并且最后一个方法在上面的示例中咱们已经用过了。让咱们再用一个简单的示例程序进一步加深对这些方法的认识。
// 列出全部用户的名称和年龄 public static void listNamesAndAges() { XMLStreamReader reader = ListUsers.getStreamReader(); try { while (reader.hasNext()) { // 跳过全部空白、注释或处理指令,到下一个START_ELEMENT int event = reader.nextTag(); if (event == XMLStreamConstants.START_ELEMENT) { if ("user".equalsIgnoreCase(reader.getLocalName())) { System.out.println("Name:" + reader.getAttributeValue(null, "name") + ";Age:" + reader.getAttributeValue(null, "age")); } } } reader.close(); } catch (XMLStreamException e) { e.printStackTrace(); } }
把它加入到主方法中:
public static void main(String[] args) { ListUsers.listNames(); // ListUsers.listNamesAndAges(); ListUsers.listAllAttrs(); }
运行结果:
相信你看到这里,已经能够顺利地使用XMLStreamReader来完成XML文档的解析了。
上面咱们介绍了基于指针的StAX API。这种方式尽管效率高,可是没有提供XML结构的抽象,所以是一种低层API。
较为高级的基于迭代器的API容许应用程序把XML做为一系列事件对象来处理,每一个对象和应用程序交换XML结构的一部分。应用程序只须要肯定解析事件的类型,将其转换成对应的具体类型,而后利用其方法得到属于该事件对象的信息。
StAX中基于迭代器的API是一种面向对象的方式,这也是它与基于指针的API的最大区别。它经过将事件转变为对象,让应用程序能够用面向对象的方式处理它们,这有利于模块化和不一样组件之间的代码重用。
事件迭代器API的主要接口是javax.xml.stream.XMLEventReader和javax.xml.stream.events.XMLEvent。XMLEventReader和XMLStreamReader相比要简单的多,这是由于关于解析事件的全部信息都封装在了事件对象(XMLEvent)中。
建立XMLEvent对象前一样须要一个XMLInputFactory实例。它有以下这些建立XMLEvent实例的方法:
XMLEventReader createXMLEventReader(java.io.InputStream stream) throws XMLStreamException; XMLEventReader createXMLEventReader(java.io.InputStream stream, String encoding) throws XMLStreamException; XMLEventReader createXMLEventReader(java.io.Reader reader) throws XMLStreamException; XMLEventReader createXMLEventReader(String systemId, java.io.InputStream stream) throws XMLStreamException; XMLEventReader createXMLEventReader(String systemId, java.io.Reader reader) throws XMLStreamException; XMLEventReader createXMLEventReader(Source source) throws XMLStreamException; XMLEventReader createXMLEventReader(XMLStreamReader reader) throws XMLStreamException;
最后一个方法不一样与其它的,它是将一个XMLStreamReader对象转换成一个XMLEventReader对象。值得注意的是,XMLInputFactory没有提供将XMLEventreader对象转换成XMLStreamreader对象的方法。我想,在咱们的开发过程当中,应该不会出现这种须要将高层API转换成低层API来使用的状况。
XMLEventReader接口扩展了java.util.Iterator接口,它定义了如下几个方法:
String getElementText() throws XMLStreamException; boolean hasNext(); XMLEvent nextEvent() throws XMLStreamException; XMLEvent nextTag() throws XMLStreamException; XMLEvent peek() throws XMLStreamException;
其中,getElementText()、hasNext()、nextTag()三个方法的含义及用法相似于XMLStreamReader,而nextEvent()方法相似于XMLStreamReader的next()方法。因此,这里只对peed()方法作一下说明。
调用peek()方法,你将获得下一个事件对象。它与nextEvent()方法的不一样是,当你连续两次或两次以上调用它时,你获得的都是同一个事件对象。
咱们再看看XMLEvent接口中定义的方法。这些方法大致能够分为三种类别。第一类是用于事件类型判断的:
第二类是将XMLEvent转换为具体的子类对象的:
第三类是获取事件对象通用信息的:
javax.xml.stream.Location getLocation();//得到事件对象的位置信息,相似于XMLStreamReader的getLocation()方法 int getEventType();//得到事件对象的类型,相似于XMLStreamReader的getEventType()方法
其中,getEventType()方法的返回值也是XMLStreamConstants中定义的常量,其类型和含义与XMLStreamReader的getEventType()方法的返回值彻底相同。
下面让咱们用一段示例代码来熟悉基于迭代器的StAX API的使用方法,进而引出XMLEvent接口的子接口类型。咱们仍然使用users.xml做为测试文件:
// 列出全部信息 @SuppressWarnings("unchecked") public static void listAllByXMLEventReader() { String xmlFile = ListUsers.class.getResource("/").getFile() + "users.xml"; XMLInputFactory factory = XMLInputFactory.newInstance(); try { // 建立基于迭代器的事件读取器对象 XMLEventReader reader = factory .createXMLEventReader(new FileReader(xmlFile)); // 遍历XML文档 while (reader.hasNext()) { XMLEvent event = reader.nextEvent(); // 若是事件对象是元素的开始 if (event.isStartElement()) { // 转换成开始元素事件对象 StartElement start = event.asStartElement(); // 打印元素标签的本地名称 System.out.print(start.getName().getLocalPart()); // 取得全部属性 Iterator attrs = start.getAttributes(); while (attrs.hasNext()) { // 打印全部属性信息 Attribute attr = (Attribute) attrs.next(); System.out.print(":" + attr.getName().getLocalPart() + "=" + attr.getValue()); } System.out.println(); } } reader.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); } }
把它加到主程序中:
public static void main(String[] args) { ListUsers.listNames(); // ListUsers.listNamesAndAges(); ListUsers.listAllAttrs(); ListUsers.listAllByXMLEventReader(); }
运行后获得以下结果:
这个例子中,咱们利用基于迭代器的StAX API打印出了全部元素的本地名称以及它们的所有属性信息。你们能够看到,它的用法与基于指针的StAX API的用法十分类似。可是因为使用了面向对象的思想,更加容易理解。
咱们用到了两个新的接口:StartElement和Attribute。它们都是XMLEvent接口的子接口,且都在javax.xml.stream.events.*包中。它们是更具体的事件对象类型。实际上在javax.xml.stream.events中,除了XMLEvent接口自身外,其他接口都是它的子接口。它们的名称和表明的具体事件对象类型以下:
Attribute:元素的属性
Characters:字符
Comment:注释
DTD:DTD
StartDocument:文档的开始
EndDocument:文档的结束
StartElement:元素的开始
EndElement:元素的结束
EntityDeclaration:实体声明
EntityReference:实体的引用
Namespace:命名空间声明
NotationDeclaration:标记的声明
ProcessingInstruction:处理指令
你可能以为这些类看着很眼熟,由于它们在XMLStreamReader的getEventType()方法的返回值,也就是XMLStreamConstants中定义的常量中,都能找到一一的对应。惟独缺乏了SAPCE(可忽略的空白)和CDATA(CDATA块)。也就是说,在基于指针的StAX API中定义事件类型,在基于迭代器的StAX API中都是以对象的形式提供给应用程序的,这就是为何说后者是一种更具备面向对象思想的高层API的缘由。
这些事件对象接口不只表明了一种事件类型,还包含对应事件对象的信息。至于它们所具备的方法大可能是获取事件对象信息的访问器,其含义及具体用法,都很容易理解和使用,所以再也不详细介绍。
你们可能注意到,XMLEvent只提供了三个asXXX()形式的方法将它转换到具体的子类型,若是你想要处理的事件对象类型在这三种类型以外,直接使用强制类型转换就能够了。
如今咱们掌握了StAX的基于指针的拉分析API和基于迭代器的拉分析API的基本应用。咱们再来看一种稍微高级的用法,它能够帮助咱们更好地完成XML文档的解析工做。
XMLInputFactory还有两个建立流读取器的方法:
XMLStreamReader createFilteredReader(XMLStreamReader reader, StreamFilter filter) throws XMLStreamException; XMLEventReader createFilteredReader(XMLEventReader reader, EventFilter filter) throws XMLStreamException;
它们分别为XMLStreamReader和XMLEventReader增长一个过滤器,过滤掉不须要解析的内容,只留下应用程序关心的信息用于解析。虽然咱们能够在应用程序中作一样的过滤工做,就像以前示例程序中所写的那样,可是把过滤工做交给过滤器的好处是,让应用程序能够更加专一于解析工做,而且对于通用的过滤(好比注释),将它放到过滤器中能够实现过滤逻辑部分代码的重用。这符合软件设计原则。
若是你编写过文件过滤器java.io.FileFilter的话,那么编写StreamFilter和EventFilter就更加容易。咱们先来看看这两个接口的定义:
public interface StreamFilter { public boolean accept(XMLStreamReader reader); } public interface EventFilter { public boolean accept(XMLEvent event); }
咱们就以StreamFilter为例来演示过滤器的用法。为此,咱们使用users.xml为测试文档编写一段新的程序:
/** * StreamFilter示例程序 * * @author zangweiren 2010-4-19 * */ public class TestStreamFilter implements StreamFilter { public static void main(String[] args) { TestStreamFilter t = new TestStreamFilter(); t.listUsers(); } @Override public boolean accept(XMLStreamReader reader) { try { while (reader.hasNext()) { int event = reader.next(); // 只接受元素的开始 if (event == XMLStreamConstants.START_ELEMENT) { // 只保留user元素 if ("user".equalsIgnoreCase(reader.getLocalName())) { return true; } } if (event == XMLStreamConstants.END_DOCUMENT) { return true; } } } catch (XMLStreamException e) { e.printStackTrace(); } return false; } public XMLStreamReader getFilteredReader() { String xmlFile = TestStreamFilter.class.getResource("/").getFile() + "users.xml"; XMLInputFactory factory = XMLInputFactory.newFactory(); XMLStreamReader reader; try { reader = factory.createXMLStreamReader(new FileReader(xmlFile)); // 建立带有过滤器的读取器实例 XMLStreamReader freader = factory .createFilteredReader(reader, this); return freader; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); } return null; } public void listUsers() { XMLStreamReader reader = getFilteredReader(); try { // 列出全部用户的名称 while (reader.hasNext()) { // 过滤工做已交由过滤器完成,这里不须要再作 System.out.println("Name=" + reader.getAttributeValue(null, "name")); if (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) { reader.next(); } } reader.close(); } catch (XMLStreamException e) { e.printStackTrace(); } } }
测试结果:
你们可能已经发现,这里有一个与以前处理不一样的地方,就是咱们先打印了用户的信息,再调用next()方法;这与java.util.Iterator的先调用next()方法,再获取对象信息不一样。而以前咱们一直采用的是与Iterator同样的处理代码。这里,就有一个问题须要说明。
对于XMLStreamReader的next()方法来讲,第一次被调用的时候返回的是第二个标记(或事件)。要得到第一个标记,就须要在调用next()方法以前调用getEventType()方法。这是须要注意的地方。咱们以上的代码之因此采用Java迭代器同样的处理方式,是由于第一个标记老是START_DOCUMENT,而咱们不须要对它进行操做,所以就采用了一种熟悉的编码方式,方便你们理解。XMLEventReader的nextEvent()方法就不存在这样的问题。
EventFilter的用法与StreamFilter相同,再也不举例说明。
StAX还为咱们提供了另一种隔离标记或事件对象过滤逻辑的方法,那就是StreamReaderDelegate和EventReaderDelegate这两个类,它们都位于javax.xml.stream.util.*包中。StAX API中大部分都是接口,这两个是确确实实的类。它们都作了一样的工做,就是分别包装了XMLStreamReader和XMLEventReader,并把全部的方法都委托(Delegate)给它们处理,既没有增长任何的方法或逻辑,也没有改变或删除任何方法,所以这里使用的是策略(Strategy)模式。咱们能够采用装饰(Decorator)模式,给StreamReaderDelegate或EventReaderDelegate增长新的功能。请看下面的例子:
/** * 测试StreamReaderDelegate * * @author zangweiren 2010-4-19 * */ public class TestStreamDelegate { public static void main(String[] args) { TestStreamDelegate t = new TestStreamDelegate(); t.listUsers(); } public XMLStreamReader getDelegateReader() { String xmlFile = TestStreamFilter.class.getResource("/").getFile() + "users.xml"; XMLInputFactory factory = XMLInputFactory.newFactory(); XMLStreamReader reader; try { reader = new StreamReaderDelegate(factory .createXMLStreamReader(new FileReader(xmlFile))) { // 重写(Override)next()方法,增长过滤逻辑 @Override public int next() throws XMLStreamException { while (true) { int event = super.next(); // 保留用户元素的开始 if (event == XMLStreamConstants.START_ELEMENT && "user".equalsIgnoreCase(getLocalName())) { return event; } else if (event == XMLStreamConstants.END_DOCUMENT) { return event; } else { continue; } } } }; return reader; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (XMLStreamException e) { e.printStackTrace(); } return null; } public void listUsers() { XMLStreamReader reader = this.getDelegateReader(); try { while (reader.hasNext()) { reader.next(); if (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) { // 列出用户的名称和年龄 System.out.println("Name=" + reader.getAttributeValue(null, "name") + ";age=" + reader.getAttributeValue(null, "age")); } } reader.close(); } catch (XMLStreamException e) { e.printStackTrace(); } } }
测试结果:
EventReaderDelegate的用法与StreamReaderDelegate相同。
如今咱们介绍完了StAX的两种解析XML文档的方式,你们也可能对它的使用有了本身的认识。咱们最后总结一下:XMLStreamReader和XMLEventReader都容许应用程序迭代底层的XML流,区别在于它们如何对外提供解析后的XML信息片断。前者像个指针,指在刚刚解析过的XML标记的后面,并提供得到关于该标记更多信息的方法。由于不用建立新的对象,因此更节约内存。后者具备更多的面向对象特征,就是个标准的Java迭代器,解析器的当前状态反映在事件对象中,应用程序在处理事件对象的时候不须要访问解析器/读取器。
关于各类XML解析技术的优劣
除了咱们刚刚介绍过的StAX这种Java 6.0新支持的XML文档解析技术以外,还有四种广为应用的解析方式,咱们将对它们作一个简要介绍,并比较五种技术的优缺点以及性能表现,以供你们在开发中选择何种解析技术作参考。
1、DOM(Document Object Model)
文档对象模型分析方式。以层次结构(相似于树型)来组织节点和信息片断,映射XML文档的结构,容许获取和操做文档的任意部分。是W3C的官方标准。
2、SAX(Simple API for XML)
流模型中的推模型分析方式。经过事件驱动,每发现一个节点就引起一个事件,经过回调方法完成解析工做,解析XML文档的逻辑须要应用程序完成。
3、JDOM(Java-based Document Object Model)
Java特定的文档对象模型。自身不包含解析器,使用SAX。
4、DOM4J(Document Object Model for Java)
简单易用,采用Java集合框架,并彻底支持DOM、SAX和JAXP。
5、StAX(Streaming API for XML)
流模型中的拉模型分析方式。提供基于指针和基于迭代器两种方式的支持。
为了比较这五种方式在解析XML文档时的性能表现,咱们来建立三个不一样大小的XML文档:smallusers.xml(100KB)、middleusers.xml(1MB)、bigusers.xml(10MB)。咱们分别用以上五种解析方式对这三个XML进行解析,而后打印出全部的用户信息,并分别计算它们所用的时间。测试代码会在文章后面的附件中给出,这里只比较它们的耗时。
单位:s(秒)
100KB | 1MB | 10MB | |
DOM | 0.146s | 0.469s | 5.876s |
SAX | 0.110s | 0.328s | 3.547s |
JDOM | 0.172s | 0.756s | 45.447s |
DOM4J | 0.161s | 0.422s | 5.103s |
StAX Stream | 0.093s | 0.334s | 3.553s |
StAX Event | 0.131s | 0.359s | 3.641s |
由上面的测试结果能够看出,性能表现最好的是SAX,其次是StAX Stream和StAX Event,DOM和DOM4J也有着不错的表现。性能最差的是JDOM。 因此,若是你的应用程序对性能的要求很高,SAX固然是首选。若是你须要访问和控制任意数据的功能,DOM是个很好的选择,而对Java开发人员来说,DOM4J是更好的选择。 若是只须要作XML文档解析的话,综合性能、易用性、面向对象特征等各方面来衡量,StAX Event无疑是最好的选择。