使用StAX pull parser 解析XML 文档

平时我们用解析Xml 文档有几种常用的方法SAX 的event 方式, DOM 的方式。  这个SAX 是基于事件模式效率性能方面还是不错的, 最大的缺陷是作为事件方式客户端没有太大控制权都是被回调EventHandler;

 

DOM 的方式在处理小XML文件的时候是很方便的可以方便的读写, 最大的问题是内存cpu 的资源占用方面太夸张了。  动不动outofmessage 实在是伤不起。 

 

现在由于有需求不想采用SAX 事件方式, 我想的是在client 端有最大的控制权限,采用跟BufferReader 的方式readline类似的接口 比如 提供 hasNext(), getNext() 去获取一个XML 元素。 这个元素是基于XPath来指定。 通过反复的google,找到点线索, 在2004年 通过了一个 JSR 173  关于StAX 的东东。 The primary goal of the StAX API is to give "parsing control to the programmer by exposing a simple iterator based API. 这个不就是俺想要的东东么。 

 

我们可以通过StAX 提供的几个接口来遍历xml stream 有两个接口可以干这个事情:

 XMLStreamReader

XMLEventReader

 

比如XMLStreamReader 的用法可以如下:

 

XMLInputFactory f = XMLInputFactory.newInstance();
XMLStreamReader r = f.createXMLStreamReader( ... );
while(r.hasNext()) {
  r.next();
} 

 

XMLEventReader 的用法

 

while(stream.hasNext()) {
XMLEvent event = stream.nextEvent();
System.out.print(event);
} 
 

我们可以通过这种方式来遍历xml 文档, 通过返回的XMLEvent 来决定是否StartElement ,EndElement 来决定当前的Element 是否我们想要的。  下面是我写的一个XMLMessageSource 类

    1,  它接受 fileName  xml 文档

    2,  path  XPath 我们需要的元素的XPath

 

 

它实现了 hasNext, getNextMessage 方法来访问下一个满足条件的Element 的String 内容

 

package com.bwang.messagefeeder.messagesource;

import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;


@SuppressWarnings("restriction")
public class XmlMessageSource implements IMessageSource {
	private static final Logger LOGGER = Logger
			.getLogger(XmlMessageSource.class);
	private final String fileName;
	private final String xmlPathPattern;
	private final String[] patternArray;
	private XMLEventReader eventReader;
	private final Stack<String> elementNames;

	public XmlMessageSource(FileEntry fileEntry, String xmlPathPattern) {
		this.fileName = fileEntry.getFileName();
		this.xmlPathPattern = xmlPathPattern;
		patternArray = makePatternArray();
		if (patternArray == null || patternArray.length ==0) {
			throw new RuntimeException("pattern is not set");
		}
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(patternArray);
		}
		elementNames = new Stack<String>();
		try {
			XMLInputFactory xif = XMLInputFactory.newInstance();
			eventReader = xif.createXMLEventReader(new FileReader(this.fileName));
		} catch (Exception e) {
			LOGGER.error("fail to create event reader", e);
		}

	}

	private String[] makePatternArray() {
		String[] tempArray = this.xmlPathPattern.split("/");
		List<String> patternStrings = new ArrayList<String>();
		for (int i=0; i< tempArray.length; i++) {
			if (!StringUtils.isEmpty(tempArray[i])) {
				patternStrings.add(tempArray[i]);
			}
		}
		return patternStrings.toArray(new String[0]);
	}

	public boolean hasNext() {
		try {
			while (eventReader.hasNext()) {
				XMLEvent xmlEvent = eventReader.peek();
				if (xmlEvent.isStartElement()) {
					StartElement elem = xmlEvent.asStartElement();
					String name = elem.getName().getLocalPart();
					elementNames.push(name);
				}
				if (match()) {
					elementNames.pop();
					return true;
				}

				else if (xmlEvent.isEndElement()) {
					elementNames.pop();
				}
				xmlEvent = eventReader.nextEvent();
			}
		} catch (Exception e) {

		}
		return false;
	}

	private boolean match() {
		if (elementNames.size() != patternArray.length) {
			return false;
		}
		for(int i=0; i< elementNames.size(); i++) {
			if (!elementNames.get(i).equalsIgnoreCase(patternArray[i])) {
				return false;
			}
		}
		return true;
	}

	public String getNextMessage() {
		try {
			return readElementBody(eventReader); 
		} catch (Exception e) {
			LOGGER.error("fail to get message", e);
			return null;
		}
	}

	public static String readElementBody(XMLEventReader eventReader)
			throws XMLStreamException {
		StringWriter buf = new StringWriter(1024);

		int depth = 0;
		do {
			// peek event
			XMLEvent xmlEvent = eventReader.peek();

			if (xmlEvent.isStartElement()) {
				++depth;
			} else if (xmlEvent.isEndElement()) {
				--depth;
			}
			// consume event
			xmlEvent = eventReader.nextEvent();
			// print out event
			xmlEvent.writeAsEncodedUnicode(buf);
		} while (depth > 0 && eventReader.hasNext());

		return buf.getBuffer().toString();
	}
	
	public void dispose() {
		if (eventReader != null) {
			try {
				eventReader.close();
			}catch(XMLStreamException e) {
			}
		}
	}


}
 

 

hasNext  会将eventReader 的游标停留在满足条件的Element 的StartElement 处。

 

调用方法是:

 

   IMessageSource ms = new XmlMessageSource(new fileentry(),  "/DOCS/Message");

     while(ms.hasNext()) {

         System.out.println(ms.getNextMessage());
    }

 

   ms.dispose();

 

通过一个大约200多M 的测试文档结构N轮的测试下来

 

    SAX Event 方式    大约  240 秒

    这个实现 大约  20秒钟不到。

    DOM 实现就表提了,直接内存爆掉。

 

终于找到一种性能秒杀SAX 的实现, 比较这个实现在测试的过程中内存占用文档在 30M 不到。

 

 

你可能感兴趣的:(parser)