DOM是解析XML文件的官方標準,它與平臺和語言無關。DOM解析將整個XML文件載入并組裝成一棵DOM節點樹,然后通過遍歷、查找節點以讀取XML文件中定義的數據。由于DOM解析中把所有節點都載入到內存中,因而它比較耗資源,而且它需要把整棵節點樹構建完成后開始讀取數據,因而它相對性能也不好;不過由于它在內存中保存了DOM節點樹,因而它可以多次讀取,并且它的節點樹定義比較容易理解,因而操作起來比較簡單。關于性能,有人對一些常用的解析方法做了比較:
單位:s(秒)轉自:http://www.cnblogs.com/hedalixin/archive/2011/12/04/2275453.html
| 100KB | 1MB | 10MB |
DOM | 0.146s | 0.469s | 5.876s |
SAX | 0.110s | 0.328s | 3.547s |
JDOM | 0.172s | 0.756s | 45.447s |
DOM4J | 0.161s | 0.422s | 5.103s |
StAX Stream | 0.093s | 0.334s | 3.553s |
StAX Event | 0.131s | 0.359s | 3.641s |
DOM樹中,所有節點都是一個Node,對不同節點有不同類型,W3C對不同類型的節點定義如下:
節點類型 | 描述 | 子元素 |
Document | 表示整個文檔(DOM 樹的根節點) | Element (max. one) ProcessingInstruction Comment DocumentType |
DocumentFragment | 表示輕量級的 Document 對象,其中容納了一部分文檔。 | ProcessingInstruction Comment Text CDATASection EntityReference |
DocumentType | 向為文檔定義的實體提供接口。 | None |
ProcessingInstruction | 表示處理指令。 | None |
EntityReference | 表示實體引用元素。 | ProcessingInstruction Comment Text CDATASection EntityReference |
Element | 表示 element(元素)元素 | Text Comment ProcessingInstruction CDATASection EntityReference |
Attr | 表示屬性。 | Text EntityReference |
Text | 表示元素或屬性中的文本內容。 | None |
CDATASection | 表示文檔中的 CDATA 區段(文本不會被解析器解析) | None |
Comment | 表示注釋。 | None |
Entity | 表示實體。 | ProcessingInstruction Comment Text CDATASection EntityReference |
Notation | 表示在 DTD 中聲明的符號。 | None |
在Java中DOM節點之間的繼承關系如下:
Node接口
在DOM中,所有節點類型都繼承自Node類,它代表在DOM樹中的一個節點。Node接口定義了一些用于處理子節點的方法,但是并不是所有的節點有子節點,比如Text節點并沒有子節點,因而向Text節點添加子節點(appendChild)會拋出DOMException。
NodeName、NodeValue、Attributes屬性
Node接口提供了getNodeName()、getNodeValue()、getAttributes()三個方法,以方便應用程序獲取這些信息,而不需要每次都轉換成不同子類以獲取這些信息。但是并不是所有的節點類型都有NodeName、NodeValue、Attributes信息,因而對那些不存在這些信息的節點可以返回null。所有節點類型對這三個方法的返回值如下表:
節點類型 | getNodeName() | getNodeValue() | getAttributes() |
Document | “#document” | null | null |
DocumentFragment | “#document-fragment” | null | null |
DocumentType | DocumentType.name | null | null |
EntityReference | 實體引用名稱 | null | null |
Element | Element.tagName(qName) | null | NamedNodeMap |
Attr | 屬性名(Attr.name) | 屬性值(Attr.value) | null |
ProcessingInstruction | ProcessingInstruction.target | ProcessingInstruction.data | null |
Comment | “#comment” | 注釋文本(CharacterData.data) | null |
Text | “#text” | 節點內容(CharacterData.data) | null |
CDATASection | “#cdata-section” | 節點內容(CharacterData.data) | null |
Entity | 實體名稱 | null | null |
Notation | 符號名稱 | null | null |
對NodeValue,Node接口還提供了setNodeValue(String nodeValue)方法,對那些getNodeValue()為null的節點類型來說,調用該方法不會有任何影響,而對非null的nodeValue,如果它是只讀的節點,調用該方法將會拋出DOMException。只有Element節點類型才有屬性信息,因而只有Element節點的getAttributes()方法能返回NamedNodeMap,Node接口還提供hasAttributes()方法以判斷當前節點是否存在屬性信息,也只有Element類型節點才會返回true。
NamedNodeMap接口實現了name=>Node的一個Map操作,對NamedNodeMap實例操作會影響其所在的Element節點中屬性的信息:
public interface NamedNodeMap {
public Node getNamedItem(String name);
public Node setNamedItem(Node arg);
public Node removeNamedItem(String name);
public Node item(int index);
public int getLength();
public Node getNamedItemNS(String namespaceURI, String localName);
public Node setNamedItemNS(Node arg);
public Node removeNamedItemNS(String namespaceURI, String localName);
}
TextContent屬性(set、get)
Node接口還提供了TextContent屬性,它以字符串的形式表示當前節點和它的所有子節點。對設置該屬性(非空非null值),它會移除當前節點的所有子節點,并用一個Text節點替代。讀取該屬性的值不會包含任何標簽字符,它也不會做任何解析,因而返回的文本會包含所有空格、換行等符號信息。對不同節點類型該屬性的內容如下:
節點類型 | TextContent |
Element、Attr、Entity、EntityReference、DocumentFragment | 將所有子節點的TextContent屬性連接在一起組成的字符串(不包含Comment、ProcessingInstruction節點),如果當前節點沒有子節點,該值為空 |
Text、CDATASection、Comment、ProcessingInstruction | NodeValue |
Document、DocumentType、Notation | null |
NodeType屬性
DOM為每種節點類型定義了一個short值:
NodeType | Named Constant | Node Value |
Element | ELEMENT_NODE | 1 |
Attr | ATTRIBUTE_NODE | 2 |
Text | TEXT_NODE | 3 |
CDATASection | CDATA_SECTION_NODE | 4 |
EntityReference | ENTITY_REFERENCE_NODE | 5 |
Entity | ENTITY_NODE | 6 |
ProcessingInstruction | PROCESSING_INSTRUCTION_NODE | 7 |
Comment | COMMENT_NODE | 8 |
Document | DOCUMENT_NODE | 9 |
DocumentType | DOCUMENT_TYPE_NODE | 10 |
DocumentFragment | DOCUMENT_FRAGMENT_NODE | 11 |
Notation | NOTATION_NODE | 12 |
在節點樹中遍歷、查找方法
getParentNode():返回當前節點的父節點。Attr、Document、DocumentFragment、Entity、Notation這些類型的節點沒有父節點。其他類型都有可能有父節點。
getFirstChild():返回當前節點的第一個子節點,如果沒有,返回null
getLastChild():返回當前節點的最后一個子節點,如果沒有,返回null
getNextSibling():返回當前節點的下一個兄弟節點,如果沒有,返回null
getPreviousSibling():返回當前節點的上一個兄弟節點,如果沒有,返回null
getOwnerDocument():返回和當前節點關聯的Document節點,一般對DOM節點樹,Document是其根節點,因而所有子節點可以通過該方法直接獲取根節點。對Document、DocumentType節點,該方法返回null。
hasChildNodes():判斷當前節點是否存在子節點。
修改子節點方法
appendChild(Node newChild):向該節點添加子節點(所有已存在的子節點之后),如果該新的節點已經在節點樹中了,該節點會先被移除。如果新節點是DocumentFragment類型,則新節點內部所有的節點都會添加到子節點列表中。由于如果新添加的節點已存在在節點樹中,該節點會先被移除,因而新節點不可以是當前節點的祖先節點或該節點本身;對不同類型的節點,其子節點的類型也是固定的,因而不可以添加了當前節點不支持的子節點;另外,對Document節點,它只能存在一個Element節點和一個DocumentType節點。
removeChild(Node oldChild):移除當前節點中的oldChild子節點,并返回該節點。
replaceChild(Node newChild, Node oldChild):將oldChild子節點替換成newChild子節點,如果newChild節點類型是DocumentFragment,則所有DocumentFragment內部的節點都會插入到oldChild節點所在的位置,最后返回oldChild子節點。如果oldChild節點已存在節點樹中,則該節點會先被移除。
insertBefore(Node newChild, Node refChild):向已存在的refChild子節點之前插入新子節點newChild。如果refChild為null,則該方法如appendChild(),即向子節點最后插入新子節點newChild。如果newChild節點為DocumentFragment,則插入的節點為DocumentFragment中的所有節點。如果newChild節點存在節點樹中,該節點會先被移除。
命名空間支持
DOM從Level2開始提供對命名空間的支持。在XML中,只有Element節點和Attr節點存在命名空間的定義,而且屬性節點(Attr)的命名空間并不默認繼承自Element節點,而它需要自己顯示的定義所在的命名空間,否則默認沒有定義命名空間。
getNamespaceURI():獲取當前節點所在的命名空間,如果沒有定義返回null。它是通過在當前作用域中查找到的值。出了Element、Attr,其他節點類型沒有命名空間定義。
getPrefix()/setPrefix(String prefix):命名空間前綴屬性,對非Element、Attr的節點,他們永遠返回null,對其設值不會有任何影響。
getLocalName():返回當前節點的本地名稱,即不包含命名空間的名字。
getBaseURI():不認識
lookupPrefix(String namespaceURI):通過namespaceURI查找命名空間前綴(prefix),從當前節點開始查找,忽略默認命名空間URI。
lookupNamespaceURI(String prefix):通過prefix查找命名空間URI,從當前節點開始查找。若prefix為null,返回默認命名空間URI。
isDefaultNamespace(String namespaceURI):判斷是否是默認的命名空間URI。
其他
isSupported(String feature, String version):返回DOM實現是否支持給定的Feature和Version。
getFeature(String feature, String version):返回實現該Feature和Version的對象,有點難理解的方法,參考其中一個簡單實現(NodeImpl):
public Object getFeature(String feature, String version) {
return isSupported(feature, version) ? this : null;
}
setUserData(String key, Object data, UserDAtaHandler handler)/getUserData(String key):向該Node中添加用戶自定義的數據,應用程序可以在接下來的邏輯中從該Node使用getUserData(String key)方法重新獲取該數據。其中UserDataHandler實例會在該Node每次被復制(Node.cloneNode())、導入(Document.importNode())、重命名(Document.renameNode())、從其他Document中引入(Document.adoptNode())、刪除(刪除在Java中的實現不可靠)時被調用:
public interface UserDataHandler {
public static final short NODE_CLONED = 1;
public static final short NODE_IMPORTED = 2;
public static final short NODE_DELETED = 3;
public static final short NODE_RENAMED = 4;
public static final short NODE_ADOPTED = 5;
public void handle(short operation, String key, Object data, Node src, Node dst);
}
cloneNode(boolean deep):拷貝當前Node,但是不會拷貝UserData屬性和ParentNode屬性。拷貝Element節點是,如果deep為false,不會拷貝所有字節點,但會拷貝所有屬性節點以及定義的具有默認值的屬性。而拷貝Attr節點,不管deep為false還是true,都會拷貝Attr的屬性值和其所有子節點。拷貝EntityReference節點時,不管deep為false還是true,都會拷貝相應的Entity。對所有其他節點類型的拷貝都是指返回自身引用。
normalize():在編程構建DOM樹時,可以構建出一棵不標準的DOM樹,比如存在兩個相鄰的Text節點,空節點之類的,normalize方法可以合并兩個相鄰的Text節點、移除空節點等。
compareDocumentPosition(Node other):比較兩個節點的相對位置,返回的short值只是一些簡單的信息,可以有如下值:
public static final short DOCUMENT_POSITION_DISCONNECTED = 0x01;
public static final short DOCUMENT_POSITION_PRECEDING = 0x02;
public static final short DOCUMENT_POSITION_FOLLOWING = 0x04;
public static final short DOCUMENT_POSITION_CONTAINS = 0x08;
public static final short DOCUMENT_POSITION_CONTAINED_BY = 0x10;
public static final short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
isSameNode(Node other):判斷兩個節點是否相同,比較引用,包括代理引用。
isEqualNode(Node other):判斷兩個節點是否相同,比較內容。normalize操作會影響該方法的結果,因而一般先調用normalize方法后,比較。parentNode、ownedDocument、userData等屬性不會影響該方法的比較,具體參考API文檔。
Document接口
Document是DOM樹的根節點,由于其他節點類型都要基于Document而存在,因而Document接口還提供了創建其他節點的工廠方法。
Document級別操作
Document子節點只能包含一個DocumentType和Element節點,因而Document提供了兩個方法直接返回這兩個節點,而對其他子節點(ProcessingInstruction和Comment)則需要通過Node接口提供的操作讀取:
public DocumentType getDoctype();
public Element getDocumentElement();
public DOMImplementation getImplementation();
對DOMImplementation接口,它提供了一些和DOM節點無關的操作:
public interface DOMImplementation {
public boolean hasFeature(String feature, String version);
public DocumentType createDocumentType(String qualifiedName,
String publicId, String systemId);
public Document createDocument(String namespaceURI, String qualifiedName,
DocumentType doctype);
public Object getFeature(String feature, String version);
}
每個XML文件都可以指定編碼類型、standalone屬性、XML版本、是否執行嚴格的語法檢查、Document的位置:
public String getInputEncoding();
public String getXmlEncoding();
public boolean getXmlStandalone();
public void setXmlStandalone(boolean xmlStandalone);
public String getXmlVersion();
public void setXmlVersion(String xmlVersion);
public boolean getStrictErrorChecking();
public void setStrictErrorChecking(boolean strictErrorChecking);
public String getDocumentURI();
public void setDocumentURI(String documentURI);
工廠方法
Document提供了創建其他所有節點類型的工廠方法,并且支持帶命名空間的Element、Attr的創建:
public Element createElement(String tagName);
public Element createElementNS(String namespaceURI, String qualifiedName);
public Attr createAttribute(String name);
public Attr createAttributeNS(String namespaceURI, String qualifiedName);
public DocumentFragment createDocumentFragment();
public Text createTextNode(String data);
public Comment createComment(String data);
public CDATASection createCDATASection(String data);
public ProcessingInstruction createProcessingInstruction(String target, String data);
public EntityReference createEntityReference(String name);
查找Element節點
1. 通過ID屬性查找:public Element getElementById(String elementId);
2. 使用標簽名查找:public NodeList getElementsByTagName(String tagname);
3. 使用命名空間URI和本地標簽名:
public NodeList getElementsByTagNameNS(String namespaceURI, String localName);
其中NodeList接口提供了遍歷Node的操作:
public interface NodeList {
public Node item(int index);
public int getLength();
}
配置與規格化
如Node.normalize(),Document也定義自己的normalizeDocument(),它根據當前的配置規格化DOM樹(替換EntityReference成Entity,合并相鄰的Text節點等),如果配置驗證信息,在操作同時還會驗證DOM樹的合法性。Document可以通過DOMConfiguration接口實例配置:
public DOMConfiguration getDomConfig();
public interface DOMConfiguration {
public void setParameter(String name, Object value);
public Object getParameter(String name);
public boolean canSetParameter(String name, Object value);
public DOMStringList getParameterNames();
}
如設置validate屬性:
DOMConfiguration docConfig = myDocument.getDomConfig();
docConfig.setParameter("validate", Boolean.TRUE);
其中DOMStringList提供了遍歷String List的操作,類似NodeList:
public interface DOMStringList {
public String item(int index);
public int getLength();
public boolean contains(String str);
}
其他
public Node importNode(Node importedNode, boolean deep);
向當前Document中導入存在于另一個Document中的節點而不改變另一個Document中DOM樹的結構。
public Node adoptNode(Node source);
向當前Document中加入存在于另一個Document中的節點,并將該節點從另一個Document中移除。
public Node renameNode(Node n, String namespaceURI, String qualifiedName);
重命名一個Element或Attr節點
Element接口
Element節點表示XML文件中的一個標簽,因而它最常用。由于在所有節點類型中,只有Element節點可以包含書,因而所有和屬性具體相關的操作都定義在Element接口中:
public String getAttribute(String name);
public void setAttribute(String name, String value);
public void removeAttribute(String name);
public Attr getAttributeNode(String name);
public Attr setAttributeNode(Attr newAttr)
public Attr removeAttributeNode(Attr oldAttr);
public String getAttributeNS(String namespaceURI, String localName);
public void setAttributeNS(String namespaceURI, String qualifiedName, String value);
public void removeAttributeNS(String namespaceURI, String localName);
public Attr getAttributeNodeNS(String namespaceURI, String localName);
public Attr setAttributeNodeNS(Attr newAttr);
public boolean hasAttribute(String name);
public boolean hasAttributeNS(String namespaceURI, String localName);
public void setIdAttribute(String name, boolean isId);
public void setIdAttributeNS(String namespaceURI, String localName, boolean isId);
public void setIdAttributeNode(Attr idAttr, boolean isId);
Element還提供了兩個使用標簽名查找子Element方法以及返回當前Element的標簽名方法,該標簽名為包含命名空間前綴的全名:
public NodeList getElementsByTagName(String name);
public NodeList getElementsByTagNameNS(String namespaceURI, String localName);
public String getTagName();
Attr接口
Attr節點表示Element節點中的一個屬性,一般一個Element中存在什么樣的屬性會在相應的Schema或DTD中定義。雖然Attr繼承自Node接口,但是它并不屬于Element節點的子節點,因而它不屬于DOM樹中的節點,它只存在于Element節點中,所以它的parentNode、previousSibling、nextSibling的值都為null。然而Attr可以存在子節點,它的子節點可以是Text節點或EntityReference節點。
對在Schema中有默認值定義的Attr,移除和初始化時會新創建一個Attr,它的specified屬性為false,值為Schema中定義的默認值。在調用Document.normalizeDocument()方法時,所有specified屬性為false的Attr值都會重新計算,如果它在Schema中沒有默認值定義,則該Attr會被移除。
屬性是具有Name和Value的值對,其中創建一個Attr實例后,Name不可以改變,要改變則需要創建新的Attr實例,而Value值可以改變。specified屬性表明該屬性的值是用戶設置的(true)還是Schema中默認提供的。id屬性表明該Attr是不是其所屬Element的id屬性,一個id屬性的值可以在一個Document中唯一的標識一個Element。由于所有Attr都是基于Element的,因而可以獲取其所屬的Element。
public interface Attr extends Node {
public String getName();
public boolean getSpecified();
public String getValue();
public void setValue(String value);
public Element getOwnerElement();
public TypeInfo getSchemaTypeInfo();
public boolean isId();
}
DocumentType接口
每個Document都有doctype屬性,它包含了DTD文件信息(位置、文件名等),同時它還提供了讀取DTD文件中定義的Entity、Notation的集合,即它是對XML文件中以下語句的封裝:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
其中:
name為beans
publicId為-//SPRING//DTD BEAN//EN
systemId為http://www.springframework.org/dtd/spring-beans.dtd
entities、notations為該DTD文件中定義的Entity和Notation的集合
public interface DocumentType extends Node {
public String getName();
public NamedNodeMap getEntities();
public NamedNodeMap getNotations();
public String getPublicId();
public String getSystemId();
public String getInternalSubset();
}
ProcessingInstruction接口
在XML文件中還可以定義一些指令以供一些解析器使用該信息對該文件做正確的處理,在DOM中使用ProcessingInstruction接口對該定義進行抽象:
<?xml-stylesheet href="show.css" type="text/css" ?>
ProcessingInstruction額外定義了兩個屬性:target和data:
target為xml-stylesheet
data為href="show.css" type="text/css"
public interface ProcessingInstruction extends Node {
public String getTarget();
public String getData();
public void setData(String data);
}
CharacterData接口
CharacterData接口繼承自Node接口,它是所有字符相關節點的父接口,定義了所有字符相關的操作:定義屬性、添加、插入、刪除、替換、取子串等。
public interface CharacterData extends Node {
public String getData();
public void setData(String data);
public int getLength();
public String substringData(int offset, int count);
public void appendData(String arg);
public void insertData(int offset, String arg);
public void deleteData(int offset, int count);
public void replaceData(int offset, int count, String arg);
}
Text接口
Text接口繼承自CharacterData接口,它表示文本節點,一般作為Element、Attr的子節點,而它本身沒有子節點。Text定義了一個文本節點,如Element的文本Content或Attr的值。若文本里面包含特殊字符(如’<’, ‘>’等)需要轉義。在操作DOM樹時,用戶可以插入多個Text節點,在Node.normalize()處理時會合并兩個各相鄰的Text節點。
Text節點提供了除對字符數據操作的其他額外操作:
splitText():Text節點分割成兩個相鄰的Text節點,即新分割出的Text節點為之前Text節點的兄弟節點
isElementContentWhitespace():判斷當前Text節點是否存在Element Content Whitespace,沒讀懂。
getWholeText():當存在多個相鄰的Text節點時,該屬性會返回所有相鄰Text節點的值。
replaceWholeText():替換所有相鄰Text節點為新設置的節點(可能是當前節點本身)。如果其中有一個節點無法移除(如包含EntityReference的節點),則會拋出DOMException。
public interface Text extends CharacterData {
public Text splitText(int offset);
public boolean isElementContentWhitespace();
public String getWholeText();
public Text replaceWholeText(String content);
}
CDATASection接口
CDATASection接口繼承自Text接口,它類似于Text節點,所不同的是所有的CDATASection節點都包含在<![CDATA[“Content need not to be escaped”]]>中,并且如果其內容包含特殊字符不需要轉義。不同于Text節點,在Node.normalize()階段,相鄰的兩個CDATASection節點不會被合并。
public interface CDATASection extends Text {
}
Comment接口
Comment接口繼承自CharacterData接口,它是對XML文件中注釋語句的抽象,它只包含注釋的字符串信息,沒有額外定義的行為:
public interface Comment extends CharacterData {
}
Entity接口
Entity接口是對一個實體定義的抽象,即它是對DTD文件中以下定義的抽象:
<!ENTITY JENN SYSTEM "http://images.about.com/sites/guidepics/html.gif" NDATA gif>
Entity接口定義了systemId、publicId、notationName等信息,而對其他信息則在其子節點中顯示,如Entity可能指向另一個外部文件,或者直接定義Entity的值(對這個,貌似我始終返回null,按網上的說法,這個是xerces的bug),如以下定義:
<!ENTITY name "cnblog">
<!ENTITY copyright SYSTEM "copyright.desc">
另外Entity還定義了一些編碼和XML版本的信息:
public interface Entity extends Node {
public String getPublicId();
public String getSystemId();
public String getNotationName();
public String getInputEncoding();
public String getXmlEncoding();
public String getXmlVersion();
}
EntityReference接口
EntityReference節點表示對一個Entity的引用。
public interface EntityReference extends Node {
}
Notation接口
Notation接口是對DTD中Notation定義的抽象:
<!NOTATION gif SYSTEM "image/gif">
一個Notation包含name(nodeName)、systemId、publicId信息:
public interface Notation extends Node {
public String getPublicId();
public String getSystemId();
}
DocumentFragment接口
DocumentFragment是對DOM樹片段的抽象,從而可以將部分DOM樹節點作為一個集合來處理,如插入到一個Document中的某個節點中,實際插入的是DocumentFragment中所有的子節點。把DOM樹的部分節點作為一個整體來看待部分可以通過Document來實現,然而在部分實現中,Document是一個重量級的對象,而DocumentFragment則可以保證它是一個輕量級的對象,因為它沒有Document存在的那么限制,這也是DocumentFragment存在的原因。DocumentFragment的定義如下:
public interface DocumentFragment extends Node {
}