xml解析之stax

博文引自: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以前,就已经有四种: 网络

  1. DOM:Document Object Model
  2. SAX:Simple API for XML
  3. JDOM:Java-based Document Object Model
  4. DOM4J:Document Object Model for Java


关于它们的解析原理,以及性能和优缺点,我会在本文的结尾作一个简要的介绍。这篇文章中,咱们主要说说StAX这种新的解析方式。 

首先咱们来搞清楚两个概念:推分析拉分析。 

在程序中访问和操做XML文件通常有两种模型:DOM(文档对象模型)和流模型。它们的优缺点以下: 

架构

引用
DOM优势:容许编辑和更新XML文档,能够随机访问文档中的数据,可使用XPath(XML Path Language,是一种从XML文档中搜索节点的查询语言)查询。 
DOM缺点:须要一次性加载整个文档到内存中,对于大型文档,会形成性能问题。



引用
流模型优势:对XML文件的访问采用流的概念,在任什么时候候内存中只有当前节点,解决了DOM的性能问题。 
流模型缺点:是只读的,而且只能向前,不能在文档中执行向后导航操做。



关于什么是DOM,文章结尾处会有介绍。这里咱们简单说一下流:它是一个连续的字节序列,能够理解为不停地从源头向目标搬运着字节的特殊对象。 

让咱们回到主题。流模型每次迭代XML文档中的一个节点,适合于处理较大的文档,所耗内存空间小。它有两种变体--“推”模型和“拉”模型。 

框架

引用
推模型:就是咱们常说的SAX,它是一种靠事件驱动的模型。当它每发现一个节点就引起一个事件,而咱们须要编写这些事件的处理程序。这样的作法很麻烦,且不灵活。



引用
拉模型:在遍历文档时,会把感兴趣的部分从读取器中拉出,不须要引起事件,容许咱们选择性地处理节点。这大大提升了灵活性,以及总体效率。



到此,咱们就弄明白了“推分析”和“拉分析”的概念: 

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有: 模块化

  • JAXP:Java API for XML Processing
  • JAXB:Java Architecture for XML Binding
  • JAX-RPC:Java API for XML-based Remote Procedure Calls
  • JAX-WS:Java API for XML Web Services
  • SAAJ:SOAP with Attachments API for Java
  • JAXR:Java API for XML Registries
  • Web Services Registry



JWSDP的早期版本中还包括: 性能

  • Java Servlet
  • JSP:JavaServer Pages
  • JSF:JavaServer Faces



如今,JWSDP已经被GlassFish所替代。 

StAX包括两套处理XML的API,分别提供了不一样程度的抽象。它们是:基于指针的API和基于迭代器的API。 

咱们先来了解基于指针的API。它把XML做为一个标记(或事件)流来处理,应用程序能够检查解析器的状态,得到解析的上一个标记的信息,而后再处理下一个标记,依次类推。 

在开始API探索以前,咱们首先建立一个名为users.xml的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

Java代码   收藏代码
  1. XMLInputFactory factory = XMLInputFactory.newInstance();  


或者: 

编码

Java代码   收藏代码
  1. XMLInputFactory factory = XMLInputFactory.newFactory();  



这两个方法是等价的,它们都是建立了一个新的实例,甚至实例的类型都是彻底一致的。由于它们的内部实现都是: 

Java代码   收藏代码
{  
    return (XMLInputFactory) FactoryFinder.find("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl");  
}  

 


接下来咱们就能够建立XMLStreamReader实例了。咱们有这样一组方法能够选择: 

Java代码   收藏代码
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是间接定位外部资源。具体一点说是这样: 

引用
systemId:外部资源(大可能是DTD文件)的URI。好比本地文件file:///user/dtd/users.dtd或者网络某个地址的文件http://www.w3.org/dtd/users.dtd。



引用
publicId:至关于一个名字,这个名字表明了一个外部资源。好比,咱们规定"W3C HTML 4.0.1"这个字符串对应"http://www.w3.org/dtd/users.dtd"这个资源。那么,publicId="W3C HTML 4.0.1"和systemId="http://www.w3.org/dtd/users.dtd"的做用就是同样的。



好了,咱们接着用以上列出的第一个接口来建立一个XMLStreamReader实例: 

Java代码   收藏代码
try {  
    XMLStreamReader reader = factory.createXMLStreamReader(new FileReader("users.xml"));  
} catch (FileNotFoundException e) {  
    e.printStackTrace();  
} catch (XMLStreamException e) {  
    e.printStackTrace();  
}  

 


要遍历XML文档,须要用到XMLStreamReader的下面几个方法: 

Java代码   收藏代码
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文档作一个测试。但愿你还记得它的内容,若是忘记了,请翻回去从新浏览一下。 

咱们的测试代码以下: 

Java代码   收藏代码
/** 
 * 列出全部用户 
 *  
 * @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();  
    }  
}  
View Code

 



运行结果: 

引用
Name:Tom 
Name:Lily 
Name:Frank 
Name:Bob 
Name:Kate



在上面的示例代码中,咱们用到了XMLStreamReader的两个新方法: 

Java代码   收藏代码
String getLocalName();  
  
String getAttributeValue(String namespaceURI, String localName);  

 


与此相关的还有一个方法: 

Java代码   收藏代码
QName getName();  

 


这三个方法牵扯到XML的namespace(命名空间)、localName(本地名称)、QName(Qualified Name,限定名称)三个概念,咱们顺便解释一下: 

命名空间是为了支持相同名称不一样含义的XML标签而产生的,它能够这么定义: 

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(统一资源名称)两种。前缀是命名空间的简写,目的是为了使用方便。命名空间被声明后就能够被使用: 

Xml代码   收藏代码
<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文档,并对特定标签进行解析了。 

咱们再来看看下面两个方法: 

Java代码   收藏代码
String getElementText() throws XMLStreamException;  
  
int nextTag() throws XMLStreamException;

 


getElementText()方法返回元素的开始标签(START_ELEMENT)和关闭标签(END_ELEMENT)之间的全部文本内容,若遇到嵌套的元素就会抛出异常。 

nextTag()方法将跳过全部空白、注释或处理指令,直到遇到START_ELEMENT或END_ELEMENT。它在解析只含元素内容的XML文档时颇有用。不然,在发现标记以前遇到非空白文本(不包括注释和处理指令),就会抛出异常。 

好比咱们修改上一个测试程序,增长一个新方法: 

Java代码   收藏代码
// 列出全部用户的名称和年龄  
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();  
    }  
}  

 



而后把它添加到主方法中: 

Java代码   收藏代码
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的某些方法,不管当前标记(或事件)是什么类型的,均可以被调用。它们的定义和做用以下: 

  • String getVersion();//得到XML文档中的版本信息
  • String getEncoding();//得到XML文档中的指定编码
  • javax.xml.namespace.NamespaceContext getNamespaceContext();//得到当前有效的命名空间上下文,包含前缀、URI等信息
  • String getNamespaceURI();//得到当前有效的命名空间的URI
  • javax.xml.stream.Location getLocation();//得到当前标记的位置信息,包含行号、列号等
  • boolean hasName();//判断当前标记是否有名称,好比元素或属性
  • boolean hasText();//判断当前标记是否有文本,好比注释、字符或CDATA
  • boolean isStartElement();//判断当前标记是不是标签开始
  • boolean isEndElement();//判断当前标记是不是标签结尾
  • boolean isCharacters();//判断当前标记是不是字符
  • boolean isWhiteSpace();//判断当前标记是不是空白



对于以上方法都很容易理解和记忆,咱们再也不编写代码展现它们的效果。 

让咱们看看有关属性操做方法。仍是首先熟悉一下它们的定义: 

Java代码   收藏代码
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);

 

  


这些方法都十分容易理解,基本上看方法的名称和参数就知道它的用途了。并且最后一个方法在上面的示例中咱们已经用过了。让咱们再用一个简单的示例程序进一步加深对这些方法的认识。 

Java代码   收藏代码
// 列出全部用户的名称和年龄  
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();  
    }  
}  

 



把它加入到主方法中: 

Java代码   收藏代码
public static void main(String[] args) {  
        ListUsers.listNames();  
        // ListUsers.listNamesAndAges();  
        ListUsers.listAllAttrs();  
}  

 



运行结果: 

引用
1.name=Tom;age=28;gender=male; 
2.name=Lily;age=26;gender=female; 
3.name=Frank;age=32;gender=male; 
4.name=Bob;age=45;gender=male; 
5.name=Kate;age=25;gender=female;



相信你看到这里,已经能够顺利地使用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实例的方法: 

Java代码   收藏代码
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接口,它定义了如下几个方法: 

Java代码   收藏代码
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接口中定义的方法。这些方法大致能够分为三种类别。第一类是用于事件类型判断的: 

  • boolean isAttribute();//判断该事件对象是不是元素的属性
  • boolean isCharacters();//判断该事件对象是不是字符
  • boolean isStartDocument();//判断该事件对象是不是文档开始
  • boolean isEndDocument();//判断该事件对象是不是文档结尾
  • boolean isStartElement();//判断该事件对象是不是元素开始
  • boolean isEndElement();//判断该事件对象是不是元素结尾
  • boolean isEntityReference();//判断该事件对象是不是实体的引用
  • boolean isNamespace();//判断该事件对象是不是命名空间
  • boolean isProcessingInstruction();//判断该事件对象是不是处理指令



第二类是将XMLEvent转换为具体的子类对象的: 

  • Characters asCharacters();//转换为字符事件对象
  • StartElement asStartElement();//转换为标签开始事件对象
  • EndElement asEndElement();//转换为标签结尾事件对象



第三类是获取事件对象通用信息的: 

javax.xml.stream.Location getLocation();//得到事件对象的位置信息,相似于XMLStreamReader的getLocation()方法
int getEventType();//得到事件对象的类型,相似于XMLStreamReader的getEventType()方法

 


其中,getEventType()方法的返回值也是XMLStreamConstants中定义的常量,其类型和含义与XMLStreamReader的getEventType()方法的返回值彻底相同。 

下面让咱们用一段示例代码来熟悉基于迭代器的StAX API的使用方法,进而引出XMLEvent接口的子接口类型。咱们仍然使用users.xml做为测试文件: 

Java代码   收藏代码
// 列出全部信息  
    @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();  
        }  
    }  

 



把它加到主程序中: 

Java代码   收藏代码
public static void main(String[] args) {  
        ListUsers.listNames();  
        // ListUsers.listNamesAndAges();  
        ListUsers.listAllAttrs();  
        ListUsers.listAllByXMLEventReader();  
    }  

 



运行后获得以下结果: 

引用
company 
depart:title=Develop Group 
user:age=28:name=Tom:gender=male 
user:age=26:name=Lily:gender=female 
depart:title=Test Group 
user:age=32:name=Frank:gender=male 
user:age=45:name=Bob:gender=male 
user:age=25:name=Kate:gender=female



这个例子中,咱们利用基于迭代器的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还有两个建立流读取器的方法: 

Java代码   收藏代码
XMLStreamReader createFilteredReader(XMLStreamReader reader, StreamFilter filter) throws XMLStreamException;  
      
XMLEventReader createFilteredReader(XMLEventReader reader, EventFilter filter) throws XMLStreamException;  

 



它们分别为XMLStreamReader和XMLEventReader增长一个过滤器,过滤掉不须要解析的内容,只留下应用程序关心的信息用于解析。虽然咱们能够在应用程序中作一样的过滤工做,就像以前示例程序中所写的那样,可是把过滤工做交给过滤器的好处是,让应用程序能够更加专一于解析工做,而且对于通用的过滤(好比注释),将它放到过滤器中能够实现过滤逻辑部分代码的重用。这符合软件设计原则。 

若是你编写过文件过滤器java.io.FileFilter的话,那么编写StreamFilter和EventFilter就更加容易。咱们先来看看这两个接口的定义: 

Java代码   收藏代码
public interface StreamFilter {  
  public boolean accept(XMLStreamReader reader);  
}  
  
public interface EventFilter {  
  public boolean accept(XMLEvent event);  
}  

 



咱们就以StreamFilter为例来演示过滤器的用法。为此,咱们使用users.xml为测试文档编写一段新的程序: 

Java代码   收藏代码
/** 
 * 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();  
        }  
    }  
  
}  
View Code

 



测试结果: 

引用
Name=Tom 
Name=Lily 
Name=Frank 
Name=Bob 
Name=Kate



你们可能已经发现,这里有一个与以前处理不一样的地方,就是咱们先打印了用户的信息,再调用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增长新的功能。请看下面的例子: 

Java代码   收藏代码
/** 
 * 测试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();  
        }  
    }  
  
}  
View Code

 


测试结果: 

引用
Name=Tom;age=28 
Name=Lily;age=26 
Name=Frank;age=32 
Name=Bob;age=45 
Name=Kate;age=25



EventReaderDelegate的用法与StreamReaderDelegate相同。 

如今咱们介绍完了StAX的两种解析XML文档的方式,你们也可能对它的使用有了本身的认识。咱们最后总结一下:XMLStreamReader和XMLEventReader都容许应用程序迭代底层的XML流,区别在于它们如何对外提供解析后的XML信息片断。前者像个指针,指在刚刚解析过的XML标记的后面,并提供得到关于该标记更多信息的方法。由于不用建立新的对象,因此更节约内存。后者具备更多的面向对象特征,就是个标准的Java迭代器,解析器的当前状态反映在事件对象中,应用程序在处理事件对象的时候不须要访问解析器/读取器。 

关于各类XML解析技术的优劣 

除了咱们刚刚介绍过的StAX这种Java 6.0新支持的XML文档解析技术以外,还有四种广为应用的解析方式,咱们将对它们作一个简要介绍,并比较五种技术的优缺点以及性能表现,以供你们在开发中选择何种解析技术作参考。

1、DOM(Document Object Model) 

文档对象模型分析方式。以层次结构(相似于树型)来组织节点和信息片断,映射XML文档的结构,容许获取和操做文档的任意部分。是W3C的官方标准。 

引用
优势: 
一、容许应用程序对数据和结构作出更改。 
二、访问是双向的,能够在任什么时候候在树中上下导航,获取和操做任意部分的数据。



引用
缺点: 
一、一般须要加载整个XML文档来构造层次结构,消耗资源大。



2、SAX(Simple API for XML) 

流模型中的推模型分析方式。经过事件驱动,每发现一个节点就引起一个事件,经过回调方法完成解析工做,解析XML文档的逻辑须要应用程序完成。 

引用
优势: 
一、不须要等待全部数据都被处理,分析就能当即开始。 
二、只在读取数据时检查数据,不须要保存在内存中。 
三、能够在某个条件获得知足时中止解析,没必要解析整个文档。 
四、效率和性能较高,能解析大于系统内存的文档。



引用
缺点: 
一、须要应用程序本身负责TAG的处理逻辑(例如维护父/子关系等),使用麻烦。 
二、单向导航,很难同时访问同一文档的不一样部分数据,不支持XPath。




3、JDOM(Java-based Document Object Model) 

Java特定的文档对象模型。自身不包含解析器,使用SAX。 

引用
优势: 
一、使用具体类而不是接口,简化了DOM的API。 
二、大量使用了Java集合类,方便了Java开发人员。



引用
缺点: 
一、没有较好的灵活性。 
二、性能较差。



4、DOM4J(Document Object Model for Java) 

简单易用,采用Java集合框架,并彻底支持DOM、SAX和JAXP。 

引用
优势: 
一、大量使用了Java集合类,方便Java开发人员,同时提供一些提升性能的替代方法。 
二、支持XPath。 
三、有很好的性能。



引用
缺点: 
一、大量使用了接口,API较为复杂。



5、StAX(Streaming API for XML) 

流模型中的拉模型分析方式。提供基于指针和基于迭代器两种方式的支持。 

引用
优势: 
一、接口简单,使用方便。 
二、采用流模型分析方式,有较好的性能。



引用
缺点: 
一、单向导航,不支持XPath,很难同时访问同一文档的不一样部分。



为了比较这五种方式在解析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无疑是最好的选择。 

相关文章
相关标签/搜索