先感谢同事renial的<解析xml时遇到的一些问题>技术分享,下面是一些记录和实际操作
1.使用Dom4j解析大文件时内存溢出的问题
问题是这样的,当我用dom4j去解析一个几十M的xml时,就出现out of memory.当然了,这也是根据你的机器性能而定的,我们都知道dom4j在各种DOM解析器中应该算是性能最好的,连大名鼎鼎的Hibernate都是用dom4j来解析XML配置文件的
问题出在于使用dom4j的SAXReader是会把整个XML文件一次性读入,如果XML文件过大就会抛出out of memory,但即使是使用SAXParser批量读入解析,但它也是一次解析完,假设XML文件有几万条数据,那么解析后就必须在内存放入这几万条对象.
常用的Dom4j文件解析方式:
InputStream is = new FileInputStream(filePath);
SAXReader reader = new SAXReader(); //将整个XML构建为一个Document
Document doc = reader.read(is);
Element root = doc.getRootElement(); // 获得根节点
for (Object obj : root.elements()) { // 遍历每个节点
Element e = (Element)obj; // 对当前节点进行操作
}
解决方法:使用ElementHandler解析文件
通过查API可以发现ElementHandler接口,下面是接口的介绍
ElementHandler interface defines a handler of Element objects. It is used primarily in event based processing models such as for
processing large XML documents as they are being parsed rather than waiting until the whole document is parsed.
好了,它就是我们想要的,通过实现以下两个method,就可以达成我们的需求
onEnd(ElementPath elementPath)
Called by an event based processor when an elements closing tag is encountered.
onStart(ElementPath elementPath)
Called by an event based processor when an elements openning tag is encountered.
下面是代码
FileInputStream fis = new FileInputStream(addPath);
SAXReader reader = new SAXReader();
ElementHandler addHandler = new MyElementHandler(); //建立 MyElementHandler 的实例
reader.addHandler("/root/test1", addHandler); // 节点
reader.addHandler("/root/test2", addHandler); // 节点
reader.read(fis);
...
class MyElementHandler implements ElementHandler {
public void onStart(ElementPath ep) {}
public void onEnd(ElementPath ep) {
Element e = ep.getCurrent(); // 获得当前节点
// 对节点进行操作。。。
e.detach(); // 处理完当前节点后,将其从dom树中剪除
}
}
因为每次处理完一个节点后并没有保存在dom树中,所以不会出现内存溢出的情况
上面的省略了一些业务代码,不知道你是否明白或有更好的方法,可以和我联系QQ:34174409
2.BOM头问题
使用java.io.Reader读取XML文件进行解析时出现异常
org.dom4j.DocumentException: Error on line 1 of document : Content is not allowed in prolog.
Nested exception:
org.xml.sax.SAXParseException: Content is not allowed in prolog.
原因在于:UTF-8编码文件存在BOM头,Reader类无法正确识别
解决方法:
(1).使用16进制编辑器手动删除BOM头
这个...自行解决
(2).InputStream读取流中前面的字符,看是否有BOM,如果有BOM,干掉BOM头
PushbackInputStream pis = new PushbackInputStream(in);
int ch = pis.read();
if (ch != 0xEF){
testin.unread(ch);
} else if ((ch = pis.read()) != 0xBB){
pis.unread(ch);
pis.unread(0xef);
} else if ((ch = pis.read()) != 0xBF){
throw new IOException("wrong format");
} else
{
}
(3).InputStream读取完文件,干掉BOM头
FileInputStream fin = new FileInputStream(fileName);
//写入临时文件
InputStream in = getInputStream(fin);
String tmpFileName = fileName + ".tmp";
FileOutputStream out = new FileOutputStream(tmpFileName);
byte b[] = new byte[4096];
int len = 0;
while (in.available() > 0){
len = in.read(b, 0, 4096);
out.write(b, 0, len);
}
in.close();
fin.close();
out.close();
//临时文件写完,开始将临时文件写回本文件。
in = new FileInputStream(tmpFileName);
System.out.println("[" + fileName + "]");
out = new FileOutputStream(fileName);
while (in.available() > 0){
len = in.read(b, 0, 4096);
out.write(b, 0, len);
}
in.close();
out.close();
(3).非法XML字符串
解析XML文件时出现非法字符的Exception(即使该字符位于CDATA段内): org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0xb) was found in the CDATA section.
原因在于:根据W3C标准,有一些字符不能出现在XML文件中:
0x00 - 0x08
0x0b - 0x0c
0x0e - 0x1f
解析XML时遇到这些字符就会出错
解决方法:
对有可能出问题的XML文件,进行字符过滤后再进行解析。
public static String stripNonValidXMLChars(String str) {
if (str == null || "".equals(str)) {
return str;
}
return str.replaceAll("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]", "");
}
----------------------------------------
by 陈于喆
QQ:34174409
Mail: chenyz@corp.netease.com