粗看之下,DOM和SAX各自的优缺点刚好形成互补。DOM使用内存保存对象结构;而SAX则基于事件并且不使用内存来存储任何数据。因此,DOM比较适合文档较小而数据访问模式复杂的情况,相反情况下,则使用SAX。
然而事实却并不这么单纯。很多情况下,开发者不情愿使用复杂的SAX,但又不得不用,因为没有其他选择。此外,即使XML文件的大小只是稍微大于几百K,DOM的内存开销和性能迟滞也会成为棘手的障碍,使得程序无法达到项目所要求的最低性能目标。
那么是否SAX的性能真得好得多?实际上,SAX所吹嘘的解析性能――通常比DOM快几倍――常常是不现实的。事实显示,SAX笨拙的,只能往前的解析不仅在使用时相当不便,而且当文档结构稍微复杂时,也会遇到性能问题。如果开发人员不想多次扫描文档,那么就需要对文档进行缓冲,或构建自己的对象模型。
1:DOM
DOM 是用与平台和语言无关的方式表示 XML 文档的官方 W3C 标准。DOM 是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而 DOM 被认为是基于树或基于对象的。DOM 以及广义的基于树的处理具有几个优点。
首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像 SAX 那样是一次性的处理。DOM 使用起来也要简单得多。
另一方面,对于特别大的文档,解析和加载整个文档可能很慢且很耗资源,因此使用其他手段来处理这样的数据会更好。这些基于事件的模型,比如 SAX。
2:SAX
这种处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX 还比它的替代者 DOM 快许多。
3:选择 DOM 还是选择 SAX ?
对于需要自己编写代码来处理 XML 文档的开发人员来说,选择 DOM 还是 SAX 解析模型是一个非常重要的设计决策。
DOM 采用建立树形结构的方式访问 XML 文档,而 SAX 采用的事件模型。
DOM 解析器把 XML 文档转化为一个包含其内容的树,并可以对树进行遍历。用 DOM 解析模型的优点是编程容易,开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用 DOM 解析器的时候需要处理整个 XML 文档,所以对性能和内存的要求比较高,尤其是遇到很大的 XML 文件的时候。由于它的遍历能力,DOM 解析器常用于 XML 文档需要频繁的改变的服务中。
SAX 解析器采用了基于事件的模型,它在解析 XML 文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX 对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX 这种扩展能力得到了更好的体现。但用 SAX 解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。
Figure 1: Various mechanism of parsing XML
使用SAX来解析XML文档
SAX APIs 是在1998年的早些时候由David Megginson提出的,目标是成为基于事件驱动的xml文档解析模式的标准API(这里你可以的到一些 SAX 的历史信息)。即使这样,SAX仍不是W3C 的REC。但毫无疑问实际中它是行业内解析XML文档的标准。
SAX 是一种基于事件的解析模式,是push-parsing原理,解析文档的时候,当遇到<opening> 标签, </closing>标签 或字符等,SAX 都会产生相应的事件(event)。一个SAX解析器解析XML文档的时候,把文档看作为一个流,依次产生相应的事件报告给已注册的content handler, org.xml.sax.ContentHandler,如果有错误,错误会报告给error handler, org.xml.sax.ErrorHandler.
如果你不注册一个error handler,那你就根本不会知道在解析XML文档的时候有没有错误产生和错误是什么。因此,在SAX解析XML文档的时候注册一个error handler是极其重要的。
如果程序需要知道有什么事件产生了(并且想处理此事件),那你必须实现org.xml.sax.ContentHandler 接口并注册给 SAX解析器。一个典型的事件被触发的顺序是
startDocument, startElement, characters, endElement, endDocument。
startDocument 仅仅被触发一次而且是在触发其它event之前。同样,endDocument仅仅被触发一次而且是在整个文档被成功解析之后。你可以从SAX javadocs中获取更详细的信息。
使用JAXP,通过SAX parse XML document的代码片断:
SAXParserFactory spfactory = SAXParserFactory.newInstance();
spfactory.setNamespaceAware(true);
SAXParser saxparser = spfactory.newSAXParser();
//write your handler for processing events and handling error
DefaultHandler handler = new MyHandler();
//parse the XML and report events and errors (if any) to the handler
saxparser.parse(new File("data.xml"), handler);
文档对象模型解析
在校验模式下进行解析
根据DTD校验
DTD 是XML 文档的语法。经常人们会觉得DTD有点另类,因为它和XML的syntax不一样,但DTD是W3C XML1.0里的完整的一部分。如果一份XML文档声明了DOCTYPE,并且想在解析的时候根据DTD校验文档,那你必须在适当的factory里启用根据DTD校验文档(validation)这个特性。例如:
DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
dbfactory.setValidating(true);
OR
SAXParserFactory spfactory = SAXParserFactory.newInstance();
spfactory.setValidating(true);
注意,如果XML文档声明了一个DTD ,即使你不启用校验(validation)这个特性,解析器总是试着去读入这个DTD。 这样做的目的是为了保证XML文档中entity reference被正确的扩展了,否则会导致格式不正确的XML文档,只有在XML文档序言部分的声明中standalone属性被置为true时,外部的DTD才会被完全忽略掉。例如:
<?xml version="1.1" encoding="UTF-8" standalone="yes"?>
根据W3C Schema来校验XML文档(WXS)
XMLSchema 是XML文档的另外一种文法描述。XMLSchema非常流行市因为它和XML文档使用同样的语法并且提供了丰富的定义校验限制的特性。如果一个XML文档用"schemaLocation" 和"noNamespaceSchemaLocation"指向了一个schema,结下来你想启用根据XMLSchema校验文档这个特性,你还要做如下的步骤:
1.和上面说的一样,调用SAXParserFactory o或DocumentBuilderFactory的setValidating函数来启用validation这个特性。
2.把属性 "http://java.sun.com/xml/jaxp/properties/schemaLanguage" 值设为 "http://www.w3.org/2001/XMLSchema"
注意,这种情况下,即使XML文档有DOCTYPE声明,处理器仍不会用DTD来校验这个文档。但是和前面提到的一样,为了任何一个entity reference是被正确扩展的,这个DTD还是会被装载的,
在.NET框架的System.XML名称空间中包含的XMLTextReader类不需要对系统资源要求很高,就能从XML文件中快速读取数据。使用XMLTextReader类能够从XML文件中读取数据,并且将其转换为HTML格式在浏览器中输出。
读本文之前,读者需要了解一些基本知识:XML、HTML、C#编程语言,以及.NET尤其是ASP.NET框架的一些知识。
微软公司的.NET框架为开发者提供了许多开发的便利,随着XML的重要性不断增长,开发者们都期待着有一整套功能强大的XML工具被开发出来。.NET框架没有辜负我们的这番期望,在System.XML 名称空间中组织进了以下几个用于XML的类:
XMLTextReader------提供以快速、单向、无缓冲的方式存取XML数据。(单向意味着你只能从前往后读取XML文件,而不能逆向读取)
XMLValidatingReader------与XMLTextReader类一起使用,提供验证DTD、XDR和XSD架构的能力。
XMLDocument------遵循W3C文档对象模型规范的一级和二级标准,实现XML数据随机的、有缓存的存取。一级水平包含了DOM的最基本的部分,而二级水平增加多种改进,包括增加了对名称空间和级连状图表(css)的支持。
XMLTextWriter------生成遵循 W3C XML 1.0 规范的XML文件。
本文主要讲述的是第一个类XMLTextReader,这个类设计的目的就是从XML文件中快速的读取数据,而对系统资源(主要包括内存和处理器时间)不做很高的要求。在父级程序的控制下,它通过每次只处理一个节点的方式对XML文件进行逐步操作,实现这种工作过程。在XML文件的每个节点中,父级程序能决定该节点的类型,它的属性和数据(如果有的话),以及其他有关该节点的信息。基于这些信息,父级程序可以选择是处理这个节点还是忽略该节点的信息,以满足各种应用程序请求的需要。这被称为抽取式(pull)处理模型,因为父级程序发出请求并且从XML文件中抽取各个节点,然后根据需要处理它或者是不处理它。
我们可以把XMLTextReader类和XML简单应用程序接口,即SAX相比,后者是在编程人员中非常流行的另一种读取XML数据的技术。XMLTextReader 和SAX有一点很相似,它们都不需要占用很多的系统资源,就能迅速的从XML文件读取数据。但是,与XMLTextReader的抽取式模型迥然不同,SAX使用的是推入式模型:XML处理器通过 “事件”告知主机应用程序哪些节点数据是可以获得,那些不能获得;根据需要,主机程序则作出相应的反应或置之不理。换句话说,数据的传送方向是从SAX处理程序中推入到主机。程序员们势必会在抽取式和推入式处理模型谁更有优势的问题上争论一番,但是大家都不可否认的是,两种模型都能很好的进行工作。.NET 框架不支持SAX,但是你能使用现存的SAX工具, 例如 MSXML分析器,用于你的.NET 程序。
XMLTextReader 类有一些构造程序来适应各种各样的情况,比如从一个已经存在的数据流或统一资源定位网址读取数据。最常见的是,你或许想从一个文件读取XML数据,那么也就有一个相应的构造程序来为此服务。这里有一个例子(我的所有代码例子都使用的是C#语言,如果你喜欢使用VISUAL BASIC语言,它们转换起来很容易)。
XMLTextReader myReader;
myReader = New XMLTextReader("c:/data/sales.XML")
创建一个称为Read()方法的循环,这个方法的返回值总是为真,直到到达文件的底部时,返回值才变为假。换句话说, 循环在文件的开始时启动并且读入所有的节点, 一次读入一个节点, 直到到达文件的结尾:
While (myReader.Read()) {
...
// 在这里处理每个节点.
...
}
每次成功调用Read()之后,XMLTextReader实例化程序包含了目前节点(即刚刚从文件中读取的那个节点)的信息。我们可以从XMLTextReader的成员中获得上述信息,就像表格1中描述的一样;并通过NodeType属性判断出当前节点的类型。在节点类型的基础上,程序的代码可以读取节点数据,检查它是否有属性,到底是忽略它还是根据程序需要进行相应的操作和处理。
当使用NodeType属性时,理解节点怎么联系到XML单元是非常重要的。例如, 看下列 XML元素:
<city>Chongqing</city>
XMLtextReader 把这个元素看作 3 个节点,顺序如下:
1.<city>标签被读为类型 XMLNodeType.Element 节点,元素的名字“city”可从 XMLTextReader 的Name属性中获得。
2.文本数据“Chongqing”被读为类型为XMLNodeType.Text的节点。数据“Chongqing ” 可从XMLTextReader 的Value属性中取得。
3.</city>标签被读为类型为XMLNodeType.EndElement 节点。同样,元素的名称“city”可从XMLTextReader的Name属性中获得。
这是 3 种重要的节点类型,其它的类型在.NET的说明文档中有详细说明,请大家参阅相关资料。
如果XMLTextReader遇到一个错误, 例如出现违反XML句法的情况,它抛出一个System.XML.XMLException类型的异常。使用这个类的代码应该总是被保护 ( 在Try……Catch块中),就像你以后在演示程序中看到的一样。
本文只是一篇相当简单的介绍XMLTextReader 类的文章,XMLTextReader类有相当多的成员,在这里不可能一一述及。当读入XML数据时,XMLTextReader能提供相当强的灵活性。即便如此,我仍然进行了大量的论述,以保证读者能编制程序来实现现实世界中经常要求完成的任务,也就是从一个XML文件读取数据然后以HTML的格式输出,从而实现在浏览器中的显示。
这个ASP.NET程序(脚本)在服务器上运行并产生一个HTML页面返回浏览器。这段脚本程序在代码段 1 给出,它用来工作使用的 XML 数据文件在代码段 2给出。你能看到这个 XML 文件包含一份表示联系关系的列表;程序的目标即是将这个列表显示出来,为了更容易我们观察,这些列表已经被格式化了。
运行程序:
1. 将代码段1存为XMLTextReader.ASPx文件,将代码段2存为XMLData.XML文件。
2. 把这两个文件都放在一个已经安装好.NET 框架的网络服务器的虚拟文件夹中。
3. 打开 Internet Explorer 并且浏览这个ASPx文件,例如,在一个局域网服务器上, URL 将是 http://localhost/xmltextreader.ASPx ;。
程序工作的大部分都由XMLDisplay 类来做,尤其是被ProcessXML()方法完成的。它每次读取一个节点XML数据,对于感兴趣的元素,节点数据和后跟冒号的节点名将和相应的HTML格式化标签一起写入输出结果中。在这阶段,“输出结果”由一个HTML文本暂时储存在其中的StringBuilder对象构成。
ProcessXML()方法是从LoadDocument()方法调用的。这个方法执行的任务是产生一个XMLTextReader实例化程序并在调用ProcessXML之前装载XML文件。它同时也处理异常,随后产生错误的信息并在浏览器中显示出来。最终该方法返回一个字符串,这个字符串或者包含产生的HTML内容,或者如果异常发生的话就包含出错信息,。
程序执行以Page_Load()程序开始,当浏览器请求浏览这个页面时,这一步会自动执行。这里的代码实例化了XMLDisplay 类并调用它的LoadDocument()方法。如果一切运行正常的话,格式化的HTML形式的返回值将被拷贝到页面的一个<div>标签中,生成的HTML文档被送回到浏览器中并显示出来。
其他的.NET 框架的类,比如XMLDocument类在读取XML数据方面表现如何呢?XMLDocument 类与XMLTextReader 类不同,它在存储器中创建整个XML文档的节点树。这样就可以随机的获得XML数据(与XMLTextReader 类获得数据的线性方式正好相反),并且在修改XML文件的数据和结构时,具有非常完美的灵活性。另外,XMLDocument允许执行XSLT 转变,不过,这些额外的功能是以运行速度的降低和系统资源的更多占用为代价的。
代码段1:XmlTextReader.aspx
<%@ Import Namespace="System.Xml" %>
<script language="C#" runat=server>
public class XmlDisplay
file://这个类读入并处理XML文件。
{
public string LoadDocument(String XmlFileName) {
XmlTextReader xmlReader = null;
StringBuilder html = new StringBuilder();
try {
file://创建XMLTextReader的实例。
xmlReader = new XmlTextReader(XmlFileName);
// 处理XML文件
html.Append(ProcessXml(xmlReader));
}
catch (XmlException ex){
html.Append("发生一个XML异常:" +
ex.ToString());
}
catch (Exception ex){
html.Append("发生一个普通异常:" +
ex.ToString());
}
finally
{
if (xmlReader != null)
xmlReader.Close();
}
return html.ToString();
}
private string ProcessXml(XmlTextReader xmlReader)
{
StringBuilder temp = new StringBuilder();
file://这个方法读入XML文件并生成输出的HTML文档。
while ( xmlReader.Read() )
{
// 处理一个元素节点的起始。
if (xmlReader.NodeType == XmlNodeType.Element)
{
file://忽略<people>和<person>元素
if ((xmlReader.Name != "person") && (xmlReader.Name != "people"))
{
file://如果是一个<category>元素,开始一个新的段落
if ( xmlReader.Name == "category" )
temp.Append("<p>");
file://添加元素名到输出中
temp.Append( xmlReader.Name + ": " );
}
}
// 处理文本节点
else if (xmlReader.NodeType == XmlNodeType.Text)
temp.Append(xmlReader.Value + "<br>");
file://处理元素节点的结尾
else if (xmlReader.NodeType == XmlNodeType.EndElement)
{
file://如果是<email>节点,添加结束段落的标记
if ( xmlReader.Name == "email" )
temp.Append("</p>");
}
}//结束while循环
return temp.ToString();
} file://结束ProcessXML方法
} file://结束XmlDisplay类
private void Page_Load(Object sender, EventArgs e){
file://创建XmlDisplay类的实例
XmlDisplay XmlDisplayDemo = new XmlDisplay();
output.InnerHtml = XmlDisplayDemo.LoadDocument(Server.MapPath("XMLData.xml"));
}
</script>
<html>
<head>
</head>
<body>
<h2>演示XmlTextReader类</h2>
<div id="output" runat="server"/>
</body>
</html>
1 static void Main(string[] args)
2 {
3 DateTime d1 =DateTime.Now;
4 XmlDocumentTest();
5 DateTime d2 =DateTime.Now;
6 TimeSpan ts =d2-d1 ;
7
8 Console.WriteLine(ts.TotalMilliseconds) ;
9 Console.Read() ;
10
11 }
12
13
14 public static string XmlFileName = "../../XML/1.xml";
15
16 private static void XmlTextReaderTest()
17 {
18 XmlTextReader reader = new XmlTextReader(XmlFileName);
19 while (reader.Read() )
20 {
21 bool exit =false;
22 switch(reader.NodeType)
23 {
24 case XmlNodeType.Element :
25 break;
26 case XmlNodeType.Text :
27 if (reader.Value=="last")
28 {
29 exit=true;
30 }
31 break;
32 case XmlNodeType.EndElement :
33 break;
34 default:
35 break;
36 }
37 if(exit)
38 {
39 return;
40
41 }
42
43 }
44 }
45
46 private static void XmlDocumentTest()
47 {
48 XmlDocument xd =new XmlDocument() ;
49 xd.Load(XmlFileName) ;
50 XmlNode node = xd.SelectSingleNode("/people/person[category='last']");
51 Console.Write(node.Name) ;
52 }
结果发现第一个耗时:
结果发现第二个耗时:
XMLTextWriter
XmlTextWriter 提供了一种快速生成 XML 的只进方法,可帮您创建 XML 文档。 XmlTextWriter 采用流式写入,而不是使用 XML 文档对象模型 (DOM) 这样的对象模型,因此具有更好的性能
Imports System.XmlC#
using System.Xml;
Dim myXmlTextWriter As XmlTextWriter = new XmlTextWriter ("newbooks.xml", System.Text.Encoding.UTF8)C#
XmlTextWriter myXmlTextWriter = new XmlTextWriter ("newbooks.xml", System.Text.Encoding.UTF8);该构造函数将获取您要写入的那个文件的文件名和您希望生成的编码。
myXmlTextWriter.Formatting = System.Xml.Formatting.IndentedC#
myXmlTextWriter.Formatting = Formatting.Indented;
myXmlTextWriter.WriteStartDocument(false)C#
myXmlTextWriter.WriteStartDocument(false);
myXmlTextWriter.WriteComment("This is a comment")
使用 WriteStartElement、WriteEndElement、WriteString 和 WriteElementString 方法创建 XML 元素节点和文本节点。 WriteElementString 方法将开始元素的编写,写出作为参数提供的字符串(如果有),并在一行之内结束此元素。
myXmlTextWriter.WriteStartElement("bookstore"); myXmlTextWriter.WriteStartElement("book", null); myXmlTextWriter.WriteElementString("title", null, "The Autobiography of Mark Twain"); myXmlTextWriter.WriteStartElement("Author", null); myXmlTextWriter.WriteElementString("first-name", "Mark"); myXmlTextWriter.WriteElementString("last-name", "Twain"); myXmlTextWriter.WriteEndElement(); myXmlTextWriter.WriteElementString("price", "7.99"); myXmlTextWriter.WriteEndElement(); myXmlTextWriter.WriteEndElement();
您可以用 WriteAttributeString 方法创建 XML 属性。 该方法使用属性的名称及其值作为参数
myXmlTextWriter.WriteStartElement("book", null); myXmlTextWriter.WriteAttributeString("genre","autobiography"); myXmlTextWriter.WriteAttributeString("publicationdate","1979"); myXmlTextWriter.WriteAttributeString("ISBN","0-7356-0562-9"); myXmlTextWriter.WriteEndElement();
以选择使用 Flush 方法将 XML 保留到一个文件中
myXmlTextWriter.Flush();
使用 Close 方法将 XML 保留到一个文件中并关闭该文件:
myXmlTextWriter.Close();