我們用 JDOM 解析 XML 最簡單的代碼莫過於以下兩行代碼,不過為了測試我們在其前後加上記錄執行時間的代碼:
long start = System.currentTimeMillis();
SAXBuilder builder = new SAXBuilder();
Document document = builder.build("struts-config.XML");
System.out.println("耗時:" + (System.currentTimeMillis()-start)+" 毫秒.");
long start = System.currentTimeMillis();
SAXBuilder builder = new SAXBuilder();
Document document = builder.build("struts-config.XML");
System.out.println("耗時:" + (System.currentTimeMillis()-start)+" 毫秒.");
在這個 struts-config.XML 中的 DTD 聲明如下:
<!DOCTYPE struts-config PUBLIC "-//apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd">
<!DOCTYPE struts-config PUBLIC "-//apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd">
正常執行時,打印出的耗時是 2698 毫秒(五次的平均值),這是能正常訪問 http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd 的情況下。假如把網線拔了,再執行上面的代碼,就會報出下面的異常:
Exception in thread "main" Java.Net.UnknownHostException: jakarta.apache.org
at Java.Net.PlainSocketImpl.connect(PlainSocketImpl.Java:177)
.......................................................
at sun.Net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.Java:977)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.Java:677)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.Java:1315)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startDTDEntity(XMLEntityManager.Java:1282)
at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.setInputSource(XMLDTDScannerImpl.Java:283)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.dispatch(XMLDocumentScannerImpl.Java:1176)
........................................................
很明顯,前面的代碼要從網絡上讀取 struts-config_1_3.dtd 來進行驗證,於是有了第一個加速的辦法:本地 DTD 驗證。從本地讀取 struts-config_1_3.dtd 文件,從 http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd 下載 struts-config_1_3.dtd 放到 struts-config.xml 同一目錄。然後修改 struts-config.XML 的 DTD 聲明如下:
<!DOCTYPE struts-config PUBLIC "-//apache Software Foundation//DTD Struts Configuration 1.3//EN"
"struts-config_1_3.dtd">
<!DOCTYPE struts-config PUBLIC "-//apache Software Foundation//DTD Struts Configuration 1.3//EN"
"struts-config_1_3.dtd">
再執行上面的代碼,打印出的耗時是 717 毫秒(五次的平均值),比前面的 2698 節省了 73.4 的時間,我所用的網絡帶寬也是能 BT 到 250 K的那種。
前面兩種情況都是進行了 DTD 驗證的情形,如果我們能給予 XML 充分信任時,就可以不進行 DTD 驗證,這是一種極端,這時候管不管你有沒有接網線都不在乎了。因此這第二種辦法就是 不進行 DTD 驗證。
查了一下 SAXBuilder 的 API http://www.jdom.org/docs/apidocs/org/jdom/input/SAXBuilder.Html,有構造方法 SAXBuilder(boolean validate) 和一個實例方法 setValidation(boolean validate) 。望文生義,似乎把參數設置為 false,就能合乎不進行 DTD 驗證的要求,可是錯了,validate 的默認值就是 false。SAXBuilder 還有一個方法 setDTDHandler(org.XML.sax.DTDHandler dtdHandler) 好像也是干這事的,於是試了一下讓 DTDHandler 無所作為:
builder.setDTDHandler(new DTDHandler(){
public void notationDecl(String name, String publicId,
String systemId) throws SAXException {
}
public void unparsedEntityDecl(String name, String publicId,
String systemId, String notationName) throws SAXException {
}
});
builder.setDTDHandler(new DTDHandler(){
public void notationDecl(String name, String publicId,
String systemId) throws SAXException {
}
public void unparsedEntityDecl(String name, String publicId,
String systemId, String notationName) throws SAXException {
}
});
可執行效果仍和本地 DTD 驗證是一樣的。也許早有人曾納悶一下前面貼出的異常為何有選擇性的。是的,關注一下中間那段 Entity 的處理。為了不進行 DTD 驗證,我們需要為 SAXBuilder 設置一個自定義的 EntityResolver。不進行 DTD 驗證的完整代碼如下:
long start = System.currentTimeMillis();
SAXBuilder builder = new SAXBuilder();
builder.setEntityResolver(new EntityResolver(){
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new StringReader(""));
}
});
Document document = builder.build("struts-config.XML");
System.out.println("耗時:" + (System.currentTimeMillis()-start)+" 毫秒.");
long start = System.currentTimeMillis();
SAXBuilder builder = new SAXBuilder();
builder.setEntityResolver(new EntityResolver(){
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new StringReader(""));
}
});
Document document = builder.build("struts-config.XML");
System.out.println("耗時:" + (System.currentTimeMillis()-start)+" 毫秒.");
現在執行上面這段代碼,打印出的耗時為 608 毫秒(五次平均值),比之 717 略有改善,不甚明顯。但有一個最大的好處是不用下載 DTD 文件至本地,繼而修改 XML 文件本身。
我時常寫起東西來,總愛循著自己的思路脈絡來寫,而非開門見山的給出答案。所以一不小心就堆出個有些走樣的長篇累牍。很考驗讀者的耐心,非得翻到最後才能知曉個究竟,估計很多人在中途被嚇跑。看過《辛德勒的名單》的朋友一定會有這樣的感受,前面 2 個多小時都是十分的沉悶,到最後部分才漸入佳境,很扣人心弦的。不太恰當的比喻,我還炮制不出這種作品來。
好啦,中間閉話了,總結一下,實際上前面那麼多加速 JDOM 解析 XML 的文字,兩言以蔽之就是:
方法 1:把 XML 中的 DTD 文件下載至本地,並修改該 XML,使之應用本地的那個 DTD 文件。網絡驗證改為本地驗證效果很明顯。
方法 2:只需為 SAXBuilder 對象設置一個返回 new InputSource(new StringReader("")) 的 EntityResolver 即可。但要自己保證 XML 的合法性了。
如果是在只有少量的 XML 文件要解析,或只在程序啟動時加載 XML 的應用中,大可不必動此干戈。而在以解析 XML 文件較為密集的應用中,這種加速就是個善舉了。比如我先前做的一個 StrutsConfigHelper,專為解析多個 struts-config.XML,然後從中查找相應配置,如果仍從網絡驗證那就很難接受了。