java xml stax_java XML -- SAX和StAX分析XML

Java与XML 之 SAX和StAX

本文由大关收集整理所得,不保证内容的正确性,转载请标明出处,谢谢!

上一次,我们讲述了使用Dom方式解析XML,并通过修改Dom树结构,最终改变XML文档内容。本次主要讲解使用SAX(Simple

API for XML)方式和StAX(Stream API for XML)方法对XML文档进行分析和生成操作。

Dom方式解析XML先要在内存中创建Dom树,然后通过遍历或修改树节点,来对XML文档中的内容进行处理,如果要将Dom树保存成XML文档需要借助Transformer,具体内容请参考我的上篇文档。Dom方式的优点在于,能够将整个文档读入到内存中,可以在任意时刻修改Dom树的任意节点的内容,并且可以使用XPath接口辅助查询和处理。但是Dom最大的缺陷是在内存中创建Dom树,如果将一个很大的XML文档读入内存,这不仅是个很耗时的操作,同时也可能造成内存空间不足,进一步恶化了XML处理速度。

SAX方式和StAX方式与Dom方式不同,它们使用的是基于流机制的处理方式。在处理XML文档时,SAX和StAX并不会将整个文档读入内存,而是,从头开始,读取一段,处理一段。SAX使用的是观察者模式(参看GOF设计模式)而StAX使用一种被称为拉(pull)的模式,稍后我们会简要探讨这两种模式的区别。

由于在处理大规模的XML文档时,DOM显得很无力,所以下面的内容将要围绕流处理机制分析XML进行探讨。(注意DOM在形成DOM树的过程中,使用SAX进行分析)。

1. SAX分析XML文档

与DOM的方式相同,很多厂商提供了对SAX的实现,我们可以通过使用SAXParserFactory来设置具体使用哪个实现的版本。之后可以通过SAX工厂实例创建一个SAXParser分析器(一般系统中都提供了默认的SAX解析器,如果没有特别必要,在不提供参数的情况下,即可使用系统的默认SAX分析器(一般是Sun提供的))。调用SAXParser的parser方法,即可实现对XML文档的分析。注意parser有两个参数,一个是要分析的XML文档的路径,另外一个是一个访问者(观察者模式)。通过对DefaultHandler中的一些方法进行重写,到达分析XML文档的目的。Demo-1给出了一个使用SAX分析XML文档的例子,在这个例子中,我们希望获取XML文档中出现的所有学号。

示例xml文档student.xml:

encoding="UTF-8"?>

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="I:\programs\eclipse\XmlTest\XMLFolder\student.xsd">

Lord

S09080408

New

York

13865445633

Math

90

Chinese

88

Mary

S09080707

Seattle

0456-5462148

Math

100

实例程序:

package com.upc.upcgrid.guan.SAXTest;

import java.io.File;

import java.io.IOException;

import

javax.xml.parsers.ParserConfigurationException;

import javax.xml.parsers.SAXParser;

import

javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;

import org.xml.sax.SAXException;

import

org.xml.sax.helpers.DefaultHandler;

public class SAXParserXMLDemo {

public static void main(String[] args)

throws ParserConfigurationException, SAXException,

IOException {

SAXParserFactory spf =

SAXParserFactory.newInstance();//创建SAX解析器工厂

spf.setNamespaceAware(true);//设置名称空间属性

SAXParser sp = spf.newSAXParser();//创建解析器

//开始解析

sp.parse(new

File(System.getProperty("user.dir")+File.separator+"XMLFolder"+File.separator+"student.xml"),

new DefaultHandler(){

boolean isStudentId = false;//是否是学号信息

@Override

public void startElement(String uri, String

localName,

String qName, Attributes attributes) throws SAXException

{

super.startElement(uri, localName, qName, attributes);

if("student_id".equals(localName))//如果接收到的元素是student_id

{

isStudentId = true;//标记这个是学号信息,在接收字符信息时需要输出学号

System.out.print(localName+" : ");//输出元素名称

}

}

@Override

public void characters(char[] ch, int

start, int length)//接收到字符信息

throws SAXException {

super.characters(ch, start, length);

if(isStudentId)//如果当前信息是学号

{

System.out.println(new

String(ch,start,length));//输出这个学号

}

}

@Override

public void endElement(String uri, String localName,

String qName)//元素终止

throws SAXException {

super.endElement(uri, localName, qName);

isStudentId = false;//标记当前元素不是学号

}

});

}

}

输出结果:

student_id : S09080408

student_id : S09080707

SAX分析过程是这样的,SAX先从XML文档开始部分进行处理,当其发现第一个元素的时候(这个例子中第一个元素是students,注意这不是第一个事件,我们仅分析关心的事件),触发一个startElement事件,并将元素的名字和所有属性都传入处理函数,而处理函数正是我们提供的DefaultHandler被重写的startElement,因此就由这个函数进行处理。所以这是一个观察者模式。SAXParser是一个观察者,他发现了访问者(DefaultHandler)关心的事件后,就将这个事件交给这个访问者处理,整个流程很像C语言中的回调函数。我们在一个元素的开始时,判断这个元素是不是student_id,如果这个元素是,那么接下来必然读到元素的文本值(因为我们知道student_id元素内仅有文本节点),会触发characters事件,然后,我们在characters中输出文本内容,最后在student_id标签结束的时候,修改状态。在DefaultHandler中还有其他的一些事件,比如文档开始,文档结束,前缀开始,前缀结束等内容,不过一般我们只关心元素开始、元素终止和字符三个事件,此外,需要注意的是在元素开始时,需要提供对这个元素的所有属性的处理(因为属性在元素开始标签内部)。关于Attributes和DefaultHandler的其他操作可以参考javaAPI文档,这里不在累述。

1.2 StAX

StAX是在java1.6之后提供的又一种XML分析方式,StAX与SAX都是基于流模式对XML文档进行分析的,但是StAX并不是使用观察者模式,而是使用一种被称为pull模式。与SAX相似,也需要先创建一个XMLInputFactory工厂,之后通过工厂创建分析器。Demo-2中给出了一个用StAX实现的与Demo-1相同功能的分析过程。

Demo-2 StAX分析示例:

package com.upc.upcgrid.guan.SAXTest;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.InputStream;

import javax.xml.stream.XMLInputFactory;

import

javax.xml.stream.XMLStreamException;

import javax.xml.stream.XMLStreamReader;

public class StAXParserXMLDemo {

public static void main(String[] args)

throws FileNotFoundException, XMLStreamException {

//创建InputStream

InputStream in = new

FileInputStream(System.getProperty("user.dir")+File.separator+"XMLFolder"+File.separator+"student.xml");

XMLInputFactory xif =

XMLInputFactory.newInstance();//创建StAX分析工厂

XMLStreamReader reader = xif.createXMLStreamReader(in);//创建分析器

while(reader.hasNext())//迭代

{

int event = reader.next();//读取下一个事件

if(event ==

XMLStreamReader.START_ELEMENT)//如果这个事件是元素开始

{

if("student_id".equals(reader.getLocalName()))//判断元素是不是student_id

{//如果是student_id则输出元素的文本内容

System.out.print(reader.getLocalName()+" : ");

System.out.println(reader.getElementText());

}

}

}

}

}

输出结果:

student_id : S09080408

student_id : S09080707

在Demo-2与Demo-1的对比中可以看出,StAX并不是使用观察者模式,StAX这种模式被称为是pull模式。在观察者模式中,分析器是主体,在整个分析过程中,分析器分析了一个事件,之后调用使用者提供的处理方法,处理产生的事件,而在pull模式中,使用者是主体,使用者调用一次分析器的next方法,分析器就产生一个新的事件,之后使用者分析这个事件,然后对这个事件进行特殊的响应。从设计角度来说,使用pull模式更令人容易接受些,但是两种设计本身并没有优劣之分,不过似乎StAX使用起来更便捷,对于普通使用者也更容易理解些,并且StAX是java1.6之后提供的,新的东西必然有其长处才会被推广。

1.3 使用StAX写入XML

StAX提供了用来写入的XMLStreamWriter类,可以使用这个类生成XML文档。这个类比较简单,这里仅提供代码,可以参考代码进行理解,更多内容请参考java

API文档。

package com.upc.upcgrid.guan.SAXTest;

import

javax.xml.stream.XMLOutputFactory;

import

javax.xml.stream.XMLStreamException;

import javax.xml.stream.XMLStreamWriter;

public class StAXGenerateXMLDemo

{

public static void writeXML(XMLStreamWriter

writer) throws XMLStreamException

{

writer.writeStartDocument("UTF-8", "1.0");//开始写文档

writer.writeStartElement("", "students");//写出一些内容

writer.writeStartElement("student");

writer.writeStartElement("student_id");

writer.writeCharacters("S09080709");

writer.writeEndElement();

writer.writeStartElement("student_name");

writer.writeCharacters("mary");

writer.writeEndElement();

writer.writeEndElement();

writer.writeStartElement("student");

writer.writeStartElement("student_id");

writer.writeCharacters("S0900121");

writer.writeEndElement();

writer.writeStartElement("student_name");

writer.writeCharacters("Lord");

writer.writeEndElement();

writer.writeEndElement();

writer.writeEndElement();

writer.writeEndDocument();//文档写出结束

writer.flush();//刷新缓冲

}

public static void main(String[] args)

throws XMLStreamException {

XMLOutputFactory xof =

XMLOutputFactory.newInstance();//创建输出工厂

XMLStreamWriter writer =

xof.createXMLStreamWriter(System.out,"UTF-8");//创建XML写出流

writer.setPrefix("", "");//没提供特殊的前缀

writeXML(writer);//执行写入一些XML信息

writer.close();//关闭写出流

}

}

输出结果:

encoding="UTF-8"?>S09080709maryS0900121Lord

注意输出的内容无法换行。更多的内容可查看java API文档,关于XMLStreamWriter的用法。

1.4 使用SAX生成XML文档

SAX没有提供写XML文档的功能,因此,我们需要像DOM一样借助Transformer将SAX内容写到XML文档中。这看起来比较复杂,Demo-4给出了一个与Demo-3功能相似,使用SAX实现的写XML文档的功能。

Demo-4 使用SAX生成XML文档

package com.upc.upcgrid.guan.SAXTest;

import java.io.IOException;

import javax.xml.transform.OutputKeys;

import javax.xml.transform.Transformer;

import

javax.xml.transform.TransformerException;

import

javax.xml.transform.TransformerFactory;

import

javax.xml.transform.sax.SAXSource;

import

javax.xml.transform.stream.StreamResult;

import org.xml.sax.ContentHandler;

import org.xml.sax.DTDHandler;

import org.xml.sax.EntityResolver;

import org.xml.sax.ErrorHandler;

import org.xml.sax.InputSource;

import org.xml.sax.SAXException;

import

org.xml.sax.SAXNotRecognizedException;

import

org.xml.sax.SAXNotSupportedException;

import org.xml.sax.XMLReader;

public class SAXGenerateXMLDemo {

static class SAXReaderImpl implements

XMLReader{

@Override

public boolean getFeature(String name)

throws SAXNotRecognizedException, SAXNotSupportedException{

return false;

}

@Override

public void setFeature(String name, boolean

value)

throws SAXNotRecognizedException, SAXNotSupportedException{

}

@Override

public Object getProperty(String name)

throws SAXNotRecognizedException, SAXNotSupportedException{

return null;

}

@Override

public void setProperty(String name, Object

value)

throws SAXNotRecognizedException, SAXNotSupportedException{

}

@Override

public void setEntityResolver(EntityResolver

resolver) {

}

@Override

public EntityResolver getEntityResolver() {

return null;

}

@Override

public void setDTDHandler(DTDHandler handler) {

}

@Override

public DTDHandler getDTDHandler() {

return null;

}

@Override

public void setContentHandler(ContentHandler handler)

{

this.handler = handler;

}

@Override

public ContentHandler getContentHandler() {

return null;

}

@Override

public void setErrorHandler(ErrorHandler handler)

{

}

@Override

public ErrorHandler getErrorHandler() {

return null;

}

@Override

public void parse(InputSource input) throws

IOException, SAXException {

if(handler == null)

throw new SAXException("content handler is

null");

handler.startDocument();

handler.startElement("", "students", "students", null);

handler.startElement("", "student", "student", null);

handler.startElement("", "student_name", "student_name",

null);

handler.characters("Mary".toCharArray(), 0, "Mary".length());

handler.endElement("", "student_name", "student_name");

handler.startElement("", "student_id", "student_id",

null);

handler.characters("S09070934".toCharArray(), 0,

"S09070934".length());

handler.endElement("", "student_id", "student_id");

handler.endElement("", "student", "student");

handler.startElement("", "student", "student", null);

handler.startElement("", "student_name", "student_name",

null);

handler.characters("Lord".toCharArray(), 0, "Mary".length());

handler.endElement("", "student_name", "student_name");

handler.startElement("", "student_id", "student_id",

null);

handler.characters("S09070808".toCharArray(), 0,

"S09070808".length());

handler.endElement("", "student_id", "student_id");

handler.endElement("", "student", "student");

handler.endElement("", "students", "students");

handler.endDocument();

}

@Override

public void parse(String systemId) throws

IOException, SAXException {

}

private ContentHandler handler;

}

public static void main(String[] args)

throws TransformerException {

TransformerFactory tff =

TransformerFactory.newInstance();

Transformer tf = tff.newTransformer();

tf.setOutputProperty(OutputKeys.INDENT, "yes");//设置转换的属性

tf.setOutputProperty(OutputKeys.METHOD, "xml");

tf.transform(new SAXSource(new

SAXReaderImpl(),null), new

StreamResult(System.out));

}

}

结果:

encoding="UTF-8"?>

Mary

S09070934

Lord

S09070808

这个实现看起来很复杂,首先是Transformer类的transform方法需要一个Source和一个Result,很明显这个Source就是我们提供的SAXSource,而SAXSource需要两个参数,第一个参数是一个XMLReader类型的实例,第二个参数是一个文件流。正常情况是XMLReader读取文件中的数据,之后调用SAXSource的ContentHandler对读取的事件进行处理。这是一种观察者模式。其中XMLReader是一个观察者,它负责处理传入的文本流,当XMLReader发现相应的事件后,调用访问者(SAXSource中的ContentHandler)的方法,完成处理过程。这里我们不需要传入流,因为我们仅是生成文档而已。XMLReader直接触发相应的事件,而SAXSource将相应的事件传递给Transformer,Transformer将事件传给ResultSource,最后输出内容。整个过程就是一个观察者模式,其中观察者是XMLReader,而访问者形成一条链,分别是ContentHandler、SAXSource、Transformer、ResultSource,这种分析模式是级联式的,很有过滤器风格(参考REST(表述状态转移))。

1.5 总结

SAX和StAX使用流模式对XML文档进行处理。这解决了DOM中无法解决的处理大规模XML文档问题。但是从上面的例子可以看出SAX和StAX这种模式在解析XML文档过程中无法修改XML文档的内容,如果要对XML中的内容进行修改,我们很可能需要解析文档,对没必要修改的内容直接拷贝到另外一个文档,而对于需要修改的内容进行特殊处理后在输出到新文档中,之后删除原来文档,将新文档改名为原来文档名字。此外基于流机制访问过的内容除非从头重新开始访问,否则无法返回访问过的节点(也因此无法使用XPath语句)。

StAX提供了专用的输出类,而DOM和SAX需要借助Transformer类(Transformer类设计不仅是输出XML,更多的是对XML文档进行转换)。StAX设计成pull模式,利于初学者理解和使用。

到目前为止,基于jaxp的三种XML文档分析模式我们已经介绍结束,但是,有一点需要提及,对于XML的验证我们并未提及,因为我工作的原因,所以对验证并不关注(也是由于验证对Schema的支持比较一般,而我又很少使用DTD),如果对XML验证感兴趣,可以参考《java

core volume II》中关于XML章节的内容。

关于XML的下一个主题就是XOM就是XML与对象映射问题。在下面几篇文章中,准备对这部分内容进行探讨。

参考:

W3School

《java与模式》

Java API官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值