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 {
}