JAVA 解析 XML 通常有兩種方式,DOM 和 SAX。DOM 雖然是 W3C 的標準,提供了標準的解析方式,但它的解析效率一直不盡如人意,因為使用DOM解析XML時,解析器讀入整個文檔并構建一個駐留內存的樹結構(節點樹),然后您的代碼才可以使用 DOM 的標準接口來操作這個樹結構。但大部分情況下我們只對文檔的部分內容感興趣,根本就不用先解析整個文檔,并且從節點樹的根節點來索引一些我們需要的數據也是非常耗時的。
SAX是一種XML解析的替代方法。相比于文檔對象模型DOM,SAX 是讀取和操作 XML 數據的更快速、更輕量的方法。SAX 允許您在讀取文檔時處理它,從而不必等待整個文檔被存儲之后才采取操作。它不涉及 DOM 所必需的開銷和概念跳躍。 SAX API是一個基于事件的API ,適用于處理數據流,即隨著數據的流動而依次處理數據。SAX API 在其解析您的文檔時發生一定事件的時候會通知您。在您對其響應時,您不作保存的數據將會 被拋棄。
下面是一個SAX解析XML的示例(有點長,因為詳細注解了SAX事件處理的所有方法),SAX API中主要有四種處理事件的接口,它們分別是ContentHandler,DTDHandler, EntityResolver 和 ErrorHandler 。下面的例子可能有點冗長,實際上只要繼承DefaultHandler 類 ,再覆蓋一部分 處理事件的方法 同樣可以達到這個示例的效果,但為了縱觀全局,還是看看SAX API里面所有主要的事件解析方法吧。( 實際上DefaultHandler就是實現了上面的四個事件處理器接口,然后提供了每個抽象方法的默認實現。)
1,ContentHandler 接口 :接收文檔邏輯內容的通知 的處理器接口。
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; class MyContentHandler implements ContentHandler{ StringBuffer jsonStringBuffer ; int frontBlankCount = 0 ; public MyContentHandler(){ jsonStringBuffer = new StringBuffer(); } /* * 接收字符數據的通知。 * 在DOM中 ch[begin:end] 相當于Text節點的節點值(nodeValue) */ @Override public void characters(char[] ch, int begin, int length) throws SAXException { StringBuffer buffer = new StringBuffer(); for(int i = begin ; i < begin+length ; i++){ switch(ch[i]){ case '\\':buffer.append("\\\\");break; case '\r':buffer.append("\\r");break; case '\n':buffer.append("\\n");break; case '\t':buffer.append("\\t");break; case '\"':buffer.append("\\\"");break; default : buffer.append(ch[i]); } } System.out.println(this.toBlankString(this.frontBlankCount)+ ">>> characters("+length+"): "+buffertoString()); } /* * 接收文檔的結尾的通知。 */ @Override public void endDocument() throws SAXException { System.out.println(this.toBlankString(--this.frontBlankCount)+ ">>> end document"); } /* * 接收文檔的結尾的通知。 * 參數意義如下: * uri :元素的命名空間 * localName :元素的本地名稱(不帶前綴) * qName :元素的限定名(帶前綴) * */ @Override public void endElement(String uri,String localName,String qName) throws SAXException { System.out.println(this.toBlankString(--this.frontBlankCount)+ ">>> end element : "+qName+"("+uri+")"); } /* * 結束前綴 URI 范圍的映射。 */ @Override public void endPrefixMapping(String prefix) throws SAXException { System.out.println(this.toBlankString(--this.frontBlankCount)+ ">>> end prefix_mapping : "+prefix); } /* * 接收元素內容中可忽略的空白的通知。 * 參數意義如下: * ch : 來自 XML 文檔的字符 * start : 數組中的開始位置 * length : 從數組中讀取的字符的個數 */ @Override public void ignorableWhitespace(char[] ch, int begin, int length) throws SAXException { StringBuffer buffer = new StringBuffer(); for(int i = begin ; i < begin+length ; i++){ switch(ch[i]){ case '\\':bufferappend("\\\\");break; case '\r':bufferappend("\\r");break; case '\n':bufferappend("\\n");break; case '\t':bufferappend("\\t");break; case '\"':bufferappend("\\\"");break; default : bufferappend(ch[i]); } } System.out.println(this.toBlankString(this.frontBlankCount)+">>> ignorable whitespace("+length+"): "+buffer.toString()); } /* * 接收處理指令的通知。 * 參數意義如下: * target : 處理指令目標 * data : 處理指令數據,如果未提供,則為 null。 */ @Override public void processingInstruction(String target,String data) throws SAXException { System.out.println(this.toBlankString(this.frontBlankCount)+">>> process instruction : (target = \"" +target+"\",data = \""+data+"\")"); } /* * 接收用來查找 SAX 文檔事件起源的對象。 * 參數意義如下: * locator : 可以返回任何 SAX 文檔事件位置的對象 */ @Override public void setDocumentLocator(Locator locator) { System.out.println(this.toBlankString(this.frontBlankCount)+ ">>> set document_locator : (lineNumber = "+locatorgetLineNumber() +",columnNumber = "+locatorgetColumnNumber() +",systemId = "+locatorgetSystemId() +",publicId = "+locatorgetPublicId()+")"); } /* * 接收跳過的實體的通知。 * 參數意義如下: * name : 所跳過的實體的名稱。如果它是參數實體,則名稱將以 '%' 開頭, * 如果它是外部 DTD 子集,則將是字符串 "[dtd]" */ @Override public void skippedEntity(String name) throws SAXException { System.out.println(this.toBlankString(this.frontBlankCount)+ ">>> skipped_entity : "+name); } /* * 接收文檔的開始的通知。 */ @Override public void startDocument() throws SAXException { System.out.println(this.toBlankString(this.frontBlankCount++)+ ">>> start document "); } /* * 接收元素開始的通知。 * 參數意義如下: * uri :元素的命名空間 * localName :元素的本地名稱(不帶前綴) * qName :元素的限定名(帶前綴) * atts :元素的屬性集合 */ @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { System.out.println(this.toBlankString(this.frontBlankCount++)+ ">>> start element : "+qName+"("+uri+")"); } /* * 開始前綴 URI 名稱空間范圍映射。 * 此事件的信息對于常規的命名空間處理并非必需: * 當 http://xmlorg/sax/features/namespaces 功能為 true(默認)時, * SAX XML 讀取器將自動替換元素和屬性名稱的前綴。 * 參數意義如下: * prefix :前綴 * uri :命名空間 */ @Override public void startPrefixMapping(String prefix,String uri) throws SAXException { System.out.println( this .toBlankString( this .frontBlankCount++)+ ">>> start prefix_mapping : xmlns:" +prefix+ " = " + "\"" +uri+ "\"" ); } private String toBlankString( int count){ StringBuffer buffer = new StringBuffer(); for ( int i = 0 ;i<count;i++) buffer.append( " " ); return buffer.toString(); } } |
2,DTDHandler 接口 :接收與 DTD 相關的事件的通知的處理器接口。
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
|
import org.xml.sax.DTDHandler; import org.xml.sax.SAXException; public class MyDTDHandler implements DTDHandler { /* * 接收注釋聲明事件的通知。 * 參數意義如下: * name - 注釋名稱。 * publicId - 注釋的公共標識符,如果未提供,則為 null。 * systemId - 注釋的系統標識符,如果未提供,則為 null。 */ @Override public void notationDecl(String name, String publicId, String systemId) throws SAXException { Systemoutprintln(">>> notation declare : (name = "+name +",systemId = "+publicId +",publicId = "+systemId+")"); } /* * 接收未解析的實體聲明事件的通知。 * 參數意義如下: * name - 未解析的實體的名稱。 * publicId - 實體的公共標識符,如果未提供,則為 null。 * systemId - 實體的系統標識符。 * notationName - 相關注釋的名稱。 */ @Override public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { Systemoutprintln( ">>> unparsed entity declare : (name = " +name + ",systemId = " +publicId + ",publicId = " +systemId + ",notationName = " +notationName+ ")" ); } } |
3,EntityResolver 接口 :是用于解析實體的基本接口。
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
|
import java.io.IOException; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; public class MyEntityResolver implements EntityResolver { /* * 允許應用程序解析外部實體。 * 解析器將在打開任何外部實體(頂級文檔實體除外)前調用此方法 * 參數意義如下: * publicId : 被引用的外部實體的公共標識符,如果未提供,則為 null。 * systemId : 被引用的外部實體的系統標識符。 * 返回: * 一個描述新輸入源的 InputSource 對象,或者返回 null, * 以請求解析器打開到系統標識符的常規 URI 連接。 */ @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return null ; } } |
4,ErrorHandler接口 :是錯誤處理程序的基本接口。
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
|
import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; public class MyErrorHandler implements ErrorHandler { /* * 接收可恢復的錯誤的通知 */ @Override public void error(SAXParseException e) throws SAXException { System.err.println("Error ("+e.getLineNumber()+"," +e.getColumnNumber()+") : "+e.getMessage()); } /* * 接收不可恢復的錯誤的通知。 */ @Override public void fatalError(SAXParseException e) throws SAXException { System.err.println("FatalError ("+e.getLineNumber()+"," +e.getColumnNumber()+") : "+e.getMessage()); } /* * 接收不可恢復的錯誤的通知。 */ @Override public void warning(SAXParseException e) throws SAXException { System.err.println( "Warning (" +e.getLineNumber()+ "," +e.getColumnNumber()+ ") : " +e.getMessage()); } } |
Test 類的主方法打印解析books.xml時的事件信息。
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
|
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import org.xml.sax.ContentHandler; import org.xml.sax.DTDHandler; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; public class Test { public static void main(String[] args) throws SAXException, FileNotFoundException, IOException { //創建處理文檔內容相關事件的處理器 ContentHandler contentHandler = new MyContentHandler(); //創建處理錯誤事件處理器 ErrorHandler errorHandler = new MyErrorHandler(); //創建處理DTD相關事件的處理器 DTDHandler dtdHandler = new MyDTDHandler(); //創建實體解析器 EntityResolver entityResolver = new MyEntityResolver(); //創建一個XML解析器(通過SAX方式讀取解析XML) XMLReader reader = XMLReaderFactory.createXMLReader(); /* * 設置解析器的相關特性 * http://xml.org/sax/features/validation = true 表示開啟驗證特性 * http://xml.org/sax/features/namespaces = true 表示開啟命名空間特性 */ reader.setFeature( " http://xml.org/sax/features/validation " , true ); reader.setFeature( " http://xml.org/sax/features/namespaces " , true ); //設置XML解析器的處理文檔內容相關事件的處理器 reader.setContentHandler(contentHandler); //設置XML解析器的處理錯誤事件處理器 reader.setErrorHandler(errorHandler); //設置XML解析器的處理DTD相關事件的處理器 reader.setDTDHandler(dtdHandler); //設置XML解析器的實體解析器 reader.setEntityResolver(entityResolver); //解析books.xml文檔 reader.parse( new InputSource( new FileReader( "books.xml" ))); } } |
books.xml 文件的內容如下:
1
|
2
3
4
5
6
7
8
9
10
11
12
13
|
<? xml version = "1.0" encoding = "GB2312" ?> < books count = "3" xmlns = " http://testorg/books " > <!--books's comment--> < book id = "1" > < name >Thinking in JAVA</ name > </ book > < book id = "2" > < name >Core JAVA2</ name > </ book > < book id = "3" > < name >C++ primer</ name > </ book > </ books > |
控制臺輸出如下:
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
|
>>> set document_locator : (lineNumber = 1,columnNumber = 1,systemId = null,publicId = null) >>> start document Error (2,7) : Document is invalid: no grammar found. Error (2,7) : Document root element "books", must match DOCTYPE root "null". >>> start prefix_mapping : xmlns: = " http://test.org/books " >>> start element : books( http://test.org/books ) >>> characters(2): \n\t >>> characters(2): \n\t >>> start element : book( http://test.org/books ) >>> characters(3): \n\t\t >>> start element : name( http://test.org/books ) >>> characters(16): Thinking in JAVA >>> end element : name( http://test.org/books ) >>> characters(2): \n\t >>> end element : book( http://test.org/books ) >>> characters(2): \n\t >>> start element : book( http://test.org/books ) >>> characters(3): \n\t\t >>> start element : name( http://test.org/books ) >>> characters(10): Core JAVA2 >>> end element : name( http://test.org/books ) >>> characters(2): \n\t >>> end element : book( http://test.org/books ) >>> characters(2): \n\t >>> start element : book( http://test.org/books ) >>> characters(3): \n\t\t >>> start element : name( http://test.org/books ) >>> characters(10): C++ primer >>> end element : name( http://test.org/books ) >>> characters(2): \n\t >>> end element : book( http://test.org/books ) >>> characters(1): \n >>> end element : books( http://test.org/books ) >>> end prefix_mapping : >>> end document |