上一篇讲解了如何使用树形存储器,本篇来讲解它是如何从xml文件中解析出一棵树的。
源码解析
下面是 parseFromXml 方法,我们就是通过这个静态方法来解析xml文件的。
public static <D> Tree<D> parseFromXml(InputStream inputStream, AbsTreeXmlHandler<D> treeXmlHandler){
try {
//1
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParser saxParser = saxParserFactory.newSAXParser();
saxParser.parse(inputStream, treeXmlHandler);
return treeXmlHandler.getResult();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
从代码1可以看出这里使用SAX来解析xml文件的。SAX是按文件字节的顺序边读取边解析的,好处是不必把整个xml文件都读入内存,内存消耗相对较少。
真正解析处理xml文件的就是传进来的 AbsTreeXmlHandler 了,结合着xml文件来分析一下代码。
<tree data="0">
<node data="1"/>
<node data="2"/>
<tree data="3">
<node data="4"/>
<node data="5"/>
<tree data="6">
<node data="7"/>
<node data="8"/>
<node data="9"/>
</tree>
</tree>
</tree>
前面说过SAX是顺序读取xml文件的,我们先按照0-1-2-3-4-5-6-7-8-9读一读,会发现SAX
是在深度优先遍历这个xml的所有结点,如此一来要把它解析成一棵树就容易多了。
要完成深度优先遍历,我们可以借助一个栈来记录当前访问结点的父结点。
public abstract class AbsTreeXmlHandler<D> extends DefaultHandler {
public static final String XML_ELEMENT_TREE = "tree";
public static final String XML_ELEMENT_NODE = "node";
public static final String XML_ELEMENT_DATA = "data";
private Tree<D> mTree;
private Stack<Tree<D>> mStack;
@Override
public void startDocument() throws SAXException {
mTree = new Tree<>();
mStack = new Stack<>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
Tree<D> parent;
D data = parseData(attributes.getValue(XML_ELEMENT_DATA));
if(mStack.empty()){
parent = null;
}else{
parent = mStack.pop();
}
switch(qName){
case XML_ELEMENT_NODE:
Tree<D> node = new Tree<>();
node.setRootData(data);
parent.insertChild(node);
mStack.push(parent);
break;
case XML_ELEMENT_TREE:
if(parent == null){
mTree.setRootData(data);
mStack.push(mTree);
return;
}
Tree<D> subTree = new Tree<>();
subTree.setRootData(data);
parent.insertChild(subTree);
//树的嵌套访问,父结点入栈
mStack.push(parent);
mStack.push(subTree);
break;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch(qName){
case XML_ELEMENT_TREE:
if(!mStack.empty()){
//整棵树访问完毕,出栈
mStack.pop();
return;
}
break;
}
}
public Tree<D> getResult(){
return mTree;
}
//1
public abstract D parseData(String sourceData);
}
代码1的抽象方法可以把读出的原始字符串结点数据转换为指定的数据类型。例如把读出的“1”字符串转换为整型数据。
优化改进
public abstract class AbsTreeXmlHandler<D> extends DefaultHandler {
...
private Stack<Tree<D>> mStack;
...
}
仔细的朋友应该发现了,AbsTreeXmlHandler使用的辅助栈是Java提供的Stack,我们来看看它的源码。
public class Stack<E> extends Vector<E> {
...
public E push(E item) {
addElement(item);
return item;
}
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
}
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess,Cloneable, java.io.Serializable{
...
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
...
}
从这里可以看出Stack是线程安全的,出栈和入栈都是同步方法,而 AbsTreeXmlHandler 里的出栈入栈并不需要担心多线程问题。同步方法无疑是降低了解析的速度,既然是这样我们可以自己写一个非线程安全的栈来代替 Stack。
public class Stack<D>{
//1
private List<D> mContainer;
public Stack(){
//2
mContainer = new LinkedList<>();
}
public void push(D data){
mContainer.add(0,data);
}
public D pop(){
return mContainer.get(0);
}
public int getSize(){
return mContainer.size();
}
public boolean empty(){
return getSize() == 0;
}
public void clear(){
mContainer.clear();
}
}
栈是线性结构,所以我们可以通过列表来实现(代码1)。如果考虑使用数组来存储数据(ArrayList),则应该在列表尾部进出数据(避免数组数据移动)。如果栈是在列表首部进出数据,则使用链表来存储数据(代码2)。
如此一来解析器的性能就得到了少许的优化了。
文章评论