we new

实现SAX、DOM4j对xml的处理

    最近需要处理xml文件,一千万条数据分在10个文件中,写入和写出都要实现。文件太大一次性加载整个document不太可能,所以就直接跳过了DOM的方式,用dom4j来实现功能的。结果跑到第一个文件的时候就崩了。修改了一波,实现了ElementHandler的onStart、onEnd方法,onEnd方法里面处理每一个item,并调用detach释放节点。跑到500万的数据又崩了一次,提示OOM: GC overhead limit exceeded,结果给jvm加了4G的内存,跑通了~原因的话,查了一些资料还没明白,先留个坑在这里,等深入理解jvm看完之后,再来复现一下堆栈看看能不能找出来。

DOM与SAX的优缺点分析

DOM

DOM是基于树的结构,通常需要加载整文档和构造DOM树,然后才能开始工作
优点:

  • 由于整棵树在内存中,因此可以对xml文档随机访问
  • 可以对xml文档进行修改操作
  • 较sax,dom使用也更简单。

缺点:

  • 整个文档必须一次性解析完
  • 由于整个文档都需要载入内存,对于大文档成本高

SAX

SAX类似流媒体,它基于事件驱动的,因此无需将整个文档载入内存,使用者只需要监听自己感兴趣的事件即可。
优点:

  • 无需将整个xml文档载入内存,因此消耗内存少
  • 可以注册多个ContentHandler

缺点:

  • 不能随机的访问xml中的节点
  • 不能修改文档

DOM4J

DOM4J提供了两种解析xml的方式:DOM解析和SAX解析。因处理的文件规模较为庞大而采用SAX的实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class myDom4j {
public static void main(String[] args) {
read("test1.xml");
}
public static void read(String fileName) {
try {
SAXReader reader = new SAXReader();
reader.addHandler("xpath", new ElementHandler() {
@Override
public void onStart(ElementPath elementPath){
//System.out.println(elementPath);
}
@Override
public void onEnd(ElementPath elementPath) {
Element row= elementPath.getCurrent();
List<Element> lstNode = row.elements();
for(Element e:lstNode) {
System.out.println(e.getQName().getName());
}
row.detach();
}
});
//step 2: 读取并处理xml
InputStream in = null;
try {
in = xIntersect.class.getClassLoader().getResourceAsStream(fileName);
System.out.println("read Start!");
reader.read(in);
System.out.println("read Finish!");
}catch (Exception e){
e.printStackTrace();
}finally {
if(in!=null)
in.close();
in = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

SAX

SAX不用将整个文档加载到内存,基于事件驱动的API(Observer模式),用户只需要注册自己感兴趣的事件即可。SAX提供EntityResolver, DTDHandler, ContentHandler, ErrorHandler接口,分别用于监听解析实体事件、DTD处理事件、正文处理事件和处理出错事件,与AWT类似,SAX还提供了一个对这4个接口默认的类DefaultHandler(这里的默认实现,其实就是一个空方法),一般只要继承DefaultHandler,重写自己感兴趣的事件即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class xSecond {
public static void main(String[] args)
{
read("test1.xml");
}
public static void read(String fileName) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
InputStream in = null;
try {
in = xUrl.class.getClassLoader().getResourceAsStream(fileName);
parser.parse(in,new DefaultHandler(){
public String name=null;
public String url=null;
public String title=null;
@Override
public void startDocument() throws SAXException {
System.out.println("开始解析文档");
}
@Override
public void endDocument() throws SAXException {
System.out.println("解析文档结束");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
name = qName;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
name ="";
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException
{
String text=new String(ch, start, length);
//获取文本节点内容
switch(name){
case "url":
url = text;break;
case "title":
if(title!=null){
title = title+text; // title 中有一些特殊字符的情况
}else{
title = text;
}break;
default:
}
}
});
}catch (Exception e){
e.printStackTrace();
}finally {
if(in!=null)
in.close();
in = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

其他小事项

一些功能性的代码没有贴出来,不过也不影响实现,主要是特定的xml文件处理方式。在实现的过程中也被自己坑到过,现在就来总结一下那些坑。

OOM与subString,split没啥关系

一开始OOM还不会用heap snap和内存分析工具,就自己查资料,看到说subString与split会造成内存的泄露,一看说的还挺有道理,就改了一波。然并卵,该错的还是没对,就看追踪源码,和分析的源码有点出入,在仔细一查,原来1.7以后的实现有改动,不是保留原来的字串了,而是直接创建一个新的。果然各种版本的改动还是得多了解了解。

关于ArrayList以及Map的一些用法

ArrayList ,string , string[] 数组的转换 以及二维数组定义ArrayList
如何用一行代码初始化一个ArrayList
java Map集合嵌套value为Map和value为List

Java实现交并补

用java求“交、叉、并集”原来这么简单,用HashSet的方式来求,在低耦合的情况下,效率高,扩展性也好。

写入xml

大量数据写入到xml文件中: http://bbs.csdn.net/topics/300177064
伪代码:

1
2
3
4
5
6
7
8
9
10
File xmlFile;
OutputStream xmlOut = new FileOutputStream(xmlFile);
xmlOut.write(root开始tag);
for(data:datas){
Document doc = dom4j jdom把一条记录data转化为dom结构
byte docBytes[] = 将doc结构转化为byte[]
xmlOut.write(docBytes);
xmlOut.write(换行);
}
xmlOut.write(root开始tag);

注意SAX的characters方法遇到空白字符的情况

因为一些element的text内容包含特殊字符,如& amp; 我尝试用org.apache.commons.lang的静态方法去处理,结果无论是html还是xml都不行。之后我就输出Unicode码,发现是分段输出的,之后猜想是characters遇到特殊字符或者是空格发生了截断(有待查源码验证),于是就加一步判断,如果为null就赋值,如果不为null,就加上之前的字串,结果字符串输出正常。

参考资料

Java解析XML汇总(DOM/SAX/JDOM/DOM4j/XPath
DOM4J解析大数据的方案
dom4j处理xml文件-saxreader与elementhandler的配合
XML解析——SAX方式
那些你一直没有搞明白的Java缓冲流细节


声明: 本文转载前需与作者联系并标明出处
分享到: