天堂的另一角

天堂魷魚的原创技术博客。所謂兼容並包,無奇不有。

 

XML解析器易用性测试对比

Java+XML是个绝配,也已经渐渐成为了当今的主流技术。笔者在实际使用中却发现XML的操作着实令人头疼,涉及到的技术繁杂零乱,甚至有些令人望而生畏。好在,这只是一种错觉。XML操作并不难,关键是选择一个合适的解析器(或者是像JAXP那样的解析层)。
XML 的常用解析器无非两种(当然还有其他的,这里就不提了):DOM和SAX。前者在解析时将整个XML文件的内容解析为一棵树,之后便可以按结点访问;而 SAX则是采用事件机制(类似Windows中的回调),轻便易用。实际上人们并不直接使用解析器,而往往是通过JAXP(Java的XML解析API) 进行解析,JAXP实际上是一个抽象层,允许以比较方便的方式去调用各种常用的解析器(包括Xerces解析器)。
除此之外,一个叫做JDom的解析器也相当入门,据说是Dom的Java专用版,相当易用。
如此多的相关东东,确实令人有些头大,所以我做了一个简易测试框架,用来对这些东西进行代码对比测试。注意,这些代码并没有测试解析器的性能,而是对比各种解析器的易用性。运行测试用的主类代码如下:

/**
@author addone@gmail.com
*
* XML测试运行类
*/
public class TestRunner {
  public static void main(String[] args) {
    ITestFrame tester;
    tester=new TestJdom(); //在这里换用其他的引擎
    try {
      output("解析器信息:" + tester.getParserInfo());
      tester.initParser("testXml/test.xml");
      output("测试1:输出XML文件原始内容:");
      tester.outputContent();
      output("测试2:增加一个结点:");
      tester.addSomething();
      tester.outputContent();
      output("测试3:编辑结点:");
      tester.editSomething();
      tester.outputContent();
      output("测试4:删除结点:");
      tester.deleteSomething();
      tester.outputContent();
    catch (Exception e) {
      output("发生异常:");
      e.printStackTrace();
    }
  }
  private static void output(String str) {
    System.out.println(str);
  }
}

从代码中可以看出,测试内容很简单,无非输出、增加、编辑、删除,而没有涉及到校验、转换等功能,但一般的使用是足以应付了,扩展也很简单。
测试运行前需要首先手动指定引擎,这很简单,改一处地方就ok了。
其中ITestFrame接口定义如下:

/**
@author addone@gmail.com
*
* XML测试框架接口

* 所有的测试类均应实现本接口并在TestRunner类中执行测试
*/
public interface ITestFrame {
public final String ERRSTR_UNSUPPORT_OPERATION="当前引擎不支持该操作";
/**
* 初始化解析器
@param xmlFilePath 目标XML文件路径
*/
public void initParser(String xmlFilePath) throws Exception;
/**
* 以表格方式输出XML文件内容
*/
public void outputContent() throws Exception;
/**
* 添加一个结点
*/
public void addSomething();
/**
* 编辑该结点
*/
public void editSomething();
/**
* 删除该结点
*/
public void deleteSomething();
/**
@return 解析器信息
*/
public String getParserInfo();
}

显然,要想实现这样的接口并不会很难。待解析的XML文档结构如下:

<通讯录>
   
<联系人 ID="1">
     
<姓名>XYZ</姓名>
     
<年龄>ZZ</年龄>
     
<类别>XX</类别>
     
<级别>YY</级别>
  
</联系人>
</通讯录>

我首先写了TestSax类来对JAXP-SAX进行测试。以下给出部分代码:

private SAXParser parser;
private String targetFilePath;
private MyParseHandler myHandler;

private class MyParseHandler extends DefaultHandler{
  
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    
if(qName=="联系人")
      System.out.print(attributes.getValue(
0)+"\t");//输出联系人ID属性
  }
  
public void characters(char[] ch, int start, int length) throws SAXException {
    String nodeValue
=new String(ch,start,length).trim();//跳过\t之类的空白符号
    if(nodeValue.length()>0)
      System.out.print(nodeValue
+"\t");//输出项目中的内容
  }
  
public void endElement(String uri, String localName, String qName) throws SAXException {
    
if(qName=="级别")
    System.out.println();
//几个项目都输出完后就回车,准备输出下一个联系人信息
  }
}

public void initParser(String xmlFilePath) throws Exception {
  parser
=SAXParserFactory.newInstance().newSAXParser();
  targetFilePath
=xmlFilePath;
  myHandler
=new MyParseHandler();
}

public void outputContent() throws Exception{
  System.out.println(
"ID\t姓名\t年龄\t类别\t级别");
  parser.parse(targetFilePath,myHandler);
}

public void addSomething() {
  System.out.println(ERRSTR_UNSUPPORT_OPERATION);
//输出不支持信息
}

其中定义了一个内部类MyParseHandler,用于实现各种事件处理。对每个元素的大致处理流程为startElement->characters->endElement。
代码很简单,Sax确实很简便。但美中不足的是不支持数据编辑功能,增加、删除之类的操作就只能输出不支持信息了。
看看JAXP-DOM怎么样:

public void initParser(String xmlFilePath) throws Exception {
  //初始化倒是和Sax一样简单
  doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFilePath);
}

public void outputContent() {
  NodeList list = doc.getElementsByTagName("联系人");
  String content = "";
  content = "ID\t姓名\t年龄\t类别\t级别\n";
  for (int i = 0; i < list.getLength(); i++) {
    content = content+ list.item(i).getAttributes().getNamedItem("ID").getNodeValue() + "\t";
    content = content+ list.item(i).getChildNodes().item(1).getFirstChild().getNodeValue() + "\t";
    content = content+ list.item(i).getChildNodes().item(3).getFirstChild().getNodeValue() + "\t";
    content = content+ list.item(i).getChildNodes().item(5).getFirstChild().getNodeValue() + "\t";
    content = content+ list.item(i).getChildNodes().item(7).getFirstChild().getNodeValue() + "\n";
  }//天哪,好长啊,看起来毫无条理,而且这些数字(1、3、5……)可都是艰苦试出来的……
  System.out.println(content);
}

public void addSomething() {
  //新建相应元素
  Element contactElement = doc.createElement("联系人");
  Element nameElement = doc.createElement("姓名");
  Element ageElement = doc.createElement("年龄");
  Element typeElement = doc.createElement("类别");
  Element levelElement = doc.createElement("级别");
  //为新元素赋值
  contactElement.setAttribute("ID""737");
  nameElement.appendChild(doc.createTextNode("tester"));
  ageElement.appendChild(doc.createTextNode("22"));
  typeElement.appendChild(doc.createTextNode("Unkown"));
  levelElement.appendChild(doc.createTextNode("Dangerous"));
  //把其他元素添加到“联系人”元素中,麻烦来了
  //看起来莫名其妙,但确实得这样,简直是令人费解,为什么非要插个“回车”结点啊?!
  contactElement.appendChild(doc.createTextNode("\n"));
  contactElement.appendChild(nameElement);
  contactElement.appendChild(doc.createTextNode("\n"));
  contactElement.appendChild(ageElement);
  contactElement.appendChild(doc.createTextNode("\n"));
  contactElement.appendChild(typeElement);
  contactElement.appendChild(doc.createTextNode("\n"));
  contactElement.appendChild(levelElement);
  contactElement.appendChild(doc.createTextNode("\n"));
  doc.getDocumentElement().appendChild(contactElement);//把“联系人”添加到根元素下
}

public void editSomething() {
  NodeList list = doc.getElementsByTagName("联系人");
  for (int i = 0; i < list.getLength(); i++) {
    if (list.item(i).getAttributes().getNamedItem("ID").getNodeValue() == "737") {
      //寻找刚才添加的那个ID为737的联系人
      list.item(i).getChildNodes().item(5).getFirstChild().setNodeValue("Changed");
      break;
    }
  }
}

public void deleteSomething() {
  NodeList list = doc.getElementsByTagName("联系人");
  for (int i = 0; i < list.getLength(); i++) {
    if (list.item(i).getAttributes().getNamedItem("ID").getNodeValue() == "737") {
      //寻找刚才添加的那个ID为737的联系人
      list.item(i).getParentNode().removeChild(list.item(i));
      break;
    }
  }
}

从代码可以看出,JAXP-DOM真的不仅仅是长而已!!这种代码简直可以用“混乱”来形容。看来,为了兼容更多的语言,DOM作出了太大的让步。如果不进行一下包装,像这样的代码根本无法用在正式开发上(几乎无法维护)。
好在,我们还有JDom,来看看:

private Element rootElement;

public void initParser(String xmlFilePath) throws Exception {
  //看起来很舒服,嗯?Sax??没错,确实是Sax
  rootElement=new SAXBuilder().build(xmlFilePath).getRootElement();
}

public void outputContent() throws Exception {
  String content = "ID\t姓名\t年龄\t类别\t级别\n";
  List nodes=rootElement.getChildren("联系人");
  for (int i = 0; i < nodes.size(); i++) {
    Element contactElement=(Element)nodes.get(i);
    content=content+contactElement.getAttributeValue("ID")+"\t";
    content=content+contactElement.getChildText("姓名")+"\t";
    content=content+contactElement.getChildText("年龄")+"\t";
    content=content+contactElement.getChildText("类别")+"\t";
    content=content+contactElement.getChildText("级别")+"\n";
  }//和Dom的代码类似,不过清晰得多了
  System.out.print(content);
}

public void addSomething() {
  //新建元素,跟通常的java对象一样
  Element contactElement = new Element("联系人");
  Element nameElement = new Element("姓名");
  Element ageElement = new Element("年龄");
  Element typeElement = new Element("类别");
  Element levelElement = new Element("级别");
  //为新元素赋值,setText()看起来很舒服
  contactElement.setAttribute("ID""737");
  nameElement.setText("tester");
  ageElement.setText("22");
  typeElement.setText("Unkown");
  levelElement.setText("Dangerous");
  //组织元素,简洁明了
  contactElement.addContent(nameElement);
  contactElement.addContent(ageElement);
  contactElement.addContent(typeElement);
  contactElement.addContent(levelElement);
  rootElement.addContent(contactElement);
}

public void editSomething() {
  List nodes=rootElement.getChildren("联系人");
  for (int i = 0; i < nodes.size(); i++) {
    Element contactElement=(Element)nodes.get(i);
    if(contactElement.getAttribute("ID").getValue()=="737")
      contactElement.getChild("类别").setText("Changed");
  }
}

public void deleteSomething() {
  List nodes=rootElement.getChildren("联系人");
  for (int i = 0; i < nodes.size(); i++) {
    Element contactElement=(Element)nodes.get(i);
    if(contactElement.getAttribute("ID").getValue()=="737")
      contactElement.getParent().removeContent(contactElement);
  }
}

代码的形式和DOM的很相似,但是明显比DOM清晰得多,好用得多了。
注 意,初始化解析器的时候我们用了SaxBuilder,事实上JDom也是可以用Dom作解析器的(改用builder就可以了),但从性能角度考虑, Sax明显要比DOM好得多,因此在新版的jdom中,已经将builder标记为“已废弃”。因此,大家最好还是用SaxBuilder吧,反正代码写 起来也没什么差别。

以上的测试框架很容易进行扩展,从而兼顾转换、验证等功能。另外,这里也没有对著名的dom4j进行测试,有兴趣的朋友可以自行尝试。
从以上代码可以看出,jdom明显具有相当的优势,而jaxp-dom则完全落于下风。再考虑到性能方面的问题,我们可以得出结论:
当只需要浏览数据时,使用jaxp-sax即可,代码简短清晰,可维护性相当高,且性能一流;如果需要兼顾数据修改、转换等一些功能,我们选用jdom更为合适。

参考链接:

JAXP全面介绍:http://www-128.ibm.com/developerworks/cn/xml/x-jaxp/
JDOM的使用:http://www.javaresearch.org/article/showarticle.jsp?column=2&thread=43416

posted on 2006-09-02 01:37 Addone 阅读(1057) 评论(1)  编辑 收藏 引用 所属分类: 软件开发

评论

# re: XML解析器易用性测试对比 2006-09-02 09:05 gyn

好文,发这儿可惜了。  回复  更多评论   

只有注册用户登录后才能发表评论。

导航

统计

公告


Addone,又名:天堂鱿鱼。
这里是我的技术博客。其他文章
作为“杂感”分类存档。
我的新思想主要发往新站:
幻想园
幻想园

欢迎光临

Linux注册用户

feedsky
抓虾
google reader
bloglines

联系方式




My status

常用链接

留言簿(11)

随笔分类(99)

随笔档案(69)

相册

友情链接

推荐站点

搜索

积分与排名

最新评论

阅读排行榜