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