使用Stax与JAXB进行XML的高效解析

本文是对我以前写过的这两篇文章的补充:

XML解析的三种方式
使用JAXB映射HashMap

在上面两篇文章中,我分别介绍了Java中常用的XML解析方式,以及如何使用JAXB实现XML和Java Class之间的映射。

在所有的XML解析方式当中,Sax和Stax比较适合在网络模块或者是处理很大的XML文件时使用,因为它们不是一次性将XML数据全部读入到内存,而是读取一部分,处理一部分,这样的方式非常高效。但是相比DOM而言,这种解析方式使得对代码变得很零碎,不能很好地用结构化的方式去处理XML。因此,如果我们可以用JAXB将Stream中的XML数据给映射成Java类,就可以同时结合Stax和JAXB的优势,实现XML的高效解析。

假设我们要处理的XML如下:

<?xml version="1.0" encoding="UTF-8"?>
<helloEntries>
    <hello>
        <name>world1</name>
    </hello>
    <hello>
        <name>world2</name>
    </hello>
    <hello>
        <name>world3</name>
    </hello>
</helloEntries>


我们可以手写,也可以用《使用JAXB映射HashMap》中介绍的在线工具生成其对应的XSD:

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="helloEntries">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="hello" maxOccurs="unbounded" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="hello">
        <xs:complexType>
            <xs:sequence>
                <xs:element type="xs:string" name="name"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>


然后使用《使用JAXB映射HashMap》一文中介绍的xjc来生成对应的Java Class:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"hello"})
@XmlRootElement(name = "helloEntries")
public class HelloEntries {

    protected List<Hello> hello;

    public List<Hello> getHello() {
        if (hello == null) {
            hello = new ArrayList<Hello>();
        }
        return this.hello;
    }

}


@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name"})
@XmlRootElement(name = "hello")
public class Hello {

    @XmlElement(required = true)
    protected String name;

    public String getName() {
        return name;
    }

    public void setName(String value) {
        this.name = value;
    }

}


接下来我们用Stax来读取XML,不同的是,在读取过程中,我们使用JAXB将每一个<hello>映射成Hello,进行数据的操作。因此,我们需要在START_ELEMENT这个EVENT当中进行JAXB绑定:

JAXBContext ctx = JAXBContext.newInstance(...);
Unmarshaller um = ctx.createUnmarshaller();


核心使用JAXB的Unmarshaller.unmarshall方法:

Object unmarshal(Reader reader)
                 throws JAXBException


注意到Unmarshaller中提供了几种不同的unmarshall函数,而我们在这里要使用的是输入参数为reader的方法,因为Stax正好是使用XMLStreamReader来提供流数据:

InputStream dataXml = ParseWithStax.class.getResourceAsStream("/helloEntries.xml");
XMLInputFactory inFactory = XMLInputFactory.newInstance();
XMLStreamReader r = inFactory.createXMLStreamReader(dataXml);


下面是完整的解析代码:

public class ParseWithStax {

    public static void main(String[] args) throws Exception {
        InputStream dataXml = ParseWithStax.class.getResourceAsStream("/helloEntries.xml");
        XMLInputFactory inFactory = XMLInputFactory.newInstance();
        XMLStreamReader r = inFactory.createXMLStreamReader(dataXml);

        JAXBContext ctx = JAXBContext.newInstance("net.bluedash.snippets.jaxb");
        Unmarshaller um = ctx.createUnmarshaller();

        try {
            int event = r.getEventType();
            while (r.hasNext()) {
                if (event == XMLStreamConstants.START_ELEMENT && r.getName().toString().equals("hello")) {
                    Hello hello = (Hello) um.unmarshal(r);
                    // process hello
                }
                event = r.next();
            }
        } finally {
            r.close();
        }

    }
}


注意这里面的核心代码是:

if (event == XMLStreamConstants.START_ELEMENT && r.getName().toString().equals("hello")) {  
    Hello hello = (Hello) um.unmarshal(r);  
    // process hello  
}  


我们让Stax在流数据处理时遇到hello元素时,使用JAXB提供的Unmashaller进行数据绑定,注意JAXB会智能的只读取一个<hello>...</hello>。

我将这个例子放在了github上面,可以签出:

git clone git://github.com/liweinan/java-snippets.git


然后运行玩玩看:

mvn install
mvn -q exec:java -Dexec.mainClass="net.bluedash.snippets.jaxb.ParseWithStax" 


参考资料

1. A JAXB Tutorial
2. Simple and efficient XML parsing using JAXB 2.0

你可能感兴趣的:(JAXB,StAX)