博文引自:http://zangweiren.iteye.com/blog/647334

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,它是一种靠事件驱动的模型。当它每发现一个节点就引发一个事件,而我们需要编写这些事件的处理程序。这样的做法很麻烦,且不灵活。



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



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

引用
基于流模型中推模型的分析方式称为推分析;基于流模型中拉模型的分析方式就称为拉分析。



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解析之stax
<?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的实例。方法是: 

Java代码  xml解析之stax
  1. XMLInputFactory factory = XMLInputFactory.newInstance();  


或者: 

Java代码  xml解析之stax
  1. XMLInputFactory factory = XMLInputFactory.newFactory();  



这两个方法是等价的,它们都是创建了一个新的实例,甚至实例的类型都是完全一致的。因为它们的内部实现都是: 

Java代码  xml解析之stax
{  
    return (XMLInputFactory) FactoryFinder.find("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl");  
}  

 


接下来我们就可以创建XMLStreamReader实例了。我们有这样一组方法可以选择: 

Java代码  xml解析之stax
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代码  xml解析之stax
try {  
    XMLStreamReader reader = factory.createXMLStreamReader(new FileReader("users.xml"));  
} catch (FileNotFoundException e) {  
    e.printStackTrace();  
} catch (XMLStreamException e) {  
    e.printStackTrace();  
}  

 


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

Java代码  xml解析之stax
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代码  xml解析之stax
/** 
 * 列出所有用户 
 *  
 * @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

 

相关文章: