使用 RestDirector™ 进行简单的高性能 XML 序列化





3.00/5 (5投票s)
RestDirector 提供 XML 的性能和可读性。

概述
XML 是一种简单且人类可读的数据通信和存储方式。作为文件,在不了解其使用它的应用程序的情况下,经常可以检查甚至修改数据。这对二进制文件来说极其困难。与二进制文件相比,使用 XML 会有一些效率低下,但其中大部分是实现问题,而不是 XML 本身的问题。
RestDirector 的 XML 序列化技术在开发人员的编码方面增加了一些负担,以换取对序列化的更大控制、消除启动延迟以及整体性能的提高。
XML 背景
SGML 被设计为“包罗万象”,但正因如此,它过于复杂,因此并未被广泛使用。HTML 是 SGML 的一个简单子集,易于理解且被广泛使用。正是 HTML 的简洁性帮助它成为了 Web 的标记语言。
XML 也源自 SGML,并被设计为一种简单易读的标记语言,对人类和计算机都友好。与 HTML 一样,XML 也已被广泛使用。
随着时间的推移,HTML 和 XML 都经历了从简单的开端变得复杂的演变。然而,大概大部分应用程序仍然只使用这两种语言的基本功能。对于 XML,术语 POX 或 Plain Old XML(纯旧 XML)已经作为实现 XML 的简单方式而获得认可。
简化 XML
XML 的大部分复杂性来自于命名空间。命名空间是允许应用程序 A 的数据与应用程序 B 的数据互操作的强大工具。许多应用程序受益于这种设计,但大多数可能不需要它,因为它们控制所有数据。命名空间允许任意节点的任何子节点在定义上与其父节点完全独立。
当您的应用程序定义所有节点,或者应用程序外部定义的节点完全包含在您应用程序的某个节点中时,命名空间不是必需的,就像您的应用程序只是外部应用程序数据的容器一样。
在处理第三方提供的数据或非常正式的环境时,模式(Schemas)也可能非常有用。但是,当您完全控制数据时,模式提供的许多内容都是多余的。任何必要的验证都可以通过您的代码在序列化过程中进行,而不是在应用程序之外的代码中进行。
RestDirector 实现 XML 的优势
.Net Framework 中 Microsoft 的 XML 序列化简单而富有创新性。然而,当数据变得庞大或复杂时,性能会受到影响。因为有很多事情是为您完成的,如果您想做一些特定的事情,您会在代码中遇到很多障碍。早期基于 RestDirector 的软件实现,当它使用 Microsoft 的 XML 序列化时,在 2007 年的一台台式机上需要 18 秒的启动时间。一旦 RestDirector 实现了自己的序列化,启动时间就缩短到四分之一秒。这种差异显而易见。
数据也必须是公共属性,并且某些项(如字典)无法直接序列化。RestDirector 可以序列化任何内容,包括私有值、字典、来自其他类的数据以及计算值。
RestDirector 还允许您的应用程序直接控制渲染顺序,以便属性和元素按照您的代码期望的方式出现,而不是自动生成的代码生成的顺序。
由于 RestDirector 将几乎所有内容都交由您的应用程序掌控,因此为独特且富有想象力的实现敞开了大门。例如,解析后的数据可以放入现有实例。条件渲染也变得简单。
由于这是您的代码而不是自动生成代码,因此您可以调试序列化过程。
RestDirector 实现 XML 的劣势
RestDirector 的 XML 序列化并非全无缺点,因为有些为您完成的事情现在需要您自己来编写代码。已经探索了自动化这些代码的技术,同时提供了自定义代码的机制。如果该项目被委托,它还可以为除自定义编码节点之外的所有节点提供自动模式生成。
RestDirector 的序列化
给定类的序列化是通过实现 DirectorWare.File.IXml 的类来完成的,该类包含四个方法
void XmlParseAttribute(DirectorWare.File.XmlFile file); void XmlParseElement(DirectorWare.File.XmlFile file); void XmlRenderAttributes(DirectorWare.File.XmlFile file); void XmlRenderElements(DirectorWare.File.XmlFile file);
显然,其中两个用于解析,两个用于渲染,每个都有一个用于属性和元素的方法。
序列化不是因为属性是公共的或被装饰的。而是调用类中的这些简单方法来解析和渲染。RestDirector 中的支持方法使序列化大多数节点只需一行代码。
渲染
通过调用“XmlRenderAttributes”和“XmlRenderElements”来渲染给定实例。这些方法按顺序简单地调用,并且不会限制类输出属性或元素。但是,将渲染分为两种方法有许多优点。
· 它在代码中区分了属性的渲染位置和元素的渲染位置。
· 属性必须首先渲染,这有助于加强这一点。
· 类可以继承,允许在不担心属性和元素排序的情况下添加新属性和元素。
简单的渲染示例
假设您的类需要生成以下 XML 片段
<Element1 Attribute1="Value1" Attribute2="123">
<Element2/>Element2’s data</Element2/>
<Element3/>Element3’s data</Element3/>
</Element1>
要渲染属性,会调用 XmlRenderAttributes。在下面的示例中,通过调用 RenderAttribute 方法来渲染两个属性。通常您会使用常量作为属性名,但由于您正在调用一个方法,因此可以选择以编程方式生成属性名。
第二个参数是属性的值,可以来自任何可用来源。它不必是公共的,也不必在此特定类中。该值被渲染为字符串,因此您可以传递一个字符串以完全控制属性的内容。渲染方法已重载,可直接处理整数、小数、DateTime、Version 等值。结果是一个方法实现,通常看起来不比此示例复杂。
public void XmlRenderAttributes(DirectorWare.File.XmlFile file)
{
file.RenderAttribute("Attribute1", this.value1);
file.RenderAttribute("Attribute2", this.value2);
}
要渲染元素,会调用 XmlRenderElements。在下面的示例中,通过调用 RenderElement 方法来渲染两个元素。这与渲染属性几乎相同,具有相同的灵活性。
当值为字符串时,您只是在渲染一个带有文本的元素。就像渲染属性一样,该方法已被重载。特别值得注意的是,您可以传递实现 DirectorWare.File.IXml 接口的对象。通过这样做,您现在有了嵌套子节点所需深度的机制。
public void XmlRenderElements(DirectorWare.File.XmlFile file)
{
file.RenderElement("Element2", this.element2);
file.RenderElement("Element3", this.element3);
}
渲染元素以提高可读性
通常,将项目分组为元素内的属性而不是单独的元素会更受欢迎。这可以极大地提高人类可读性并减少 XML 大小。RestDirector 中的诊断提供了一个示例。Environment 类提供了应用程序环境的详细列表,可能会很长。有关主机操作系统的信息可以呈现为如下元素:
<Environment>
<OSPlatform>Win32NT</OSPlatform>
<OSVersion>6.0.6001.65536</OSVersion>
</Environment>
为了缩小 XML 并使其更具可读性,我们将其渲染为:
<Environment>
<OS Platform="Win32NT" Version="6.0.6001.65536"/>
</Environment>
Environment 类有十几个节点,本示例仅显示两个。显然,大规模进行此操作可以显著缩小 XML 并提高人类可读性。
要在单个类中渲染此内容,我们可以在 Environment 类中执行以下操作:
public void XmlRenderElements(DirectorWare.File.XmlFile file)
{
file.RenderElement("OS", this.xmlRenderOS);
}
private void xmlRenderOS(DirectorWare.File.XmlFile file)
{
file.RenderAttribute("Platform", this.osPlatform);
file.RenderAttribute("Version", this.osVersion);
}
这通过传递基于 XmlFileMethod 委托的方法来实现嵌套渲染。该委托允许我们使用 XmlFile 参数调用方法。现在我们可以向类的元素添加子元素,而无需实例化其他类。当我们想将一个对象传递给方法时,可以使用 XmlFileMethodValue 委托。
渲染列表
渲染列表可以根据需要简单或复杂。此示例来自 RestDirector 的异常日志记录。
public void XmlRenderElements(DirectorWare.File.XmlFile file)
{
foreach (ExceptionStackTrace stackTrace in this.stackTraceList)
file.RenderElement("Stack", stackTrace);
}
在这种情况下,会根据需要生成一系列名为“Stack”的元素。必须编写渲染代码才能为其他处理(如过滤和排序)打开大门。
渲染字典
由于我们在渲染过程中自己迭代列表,因此现在可以迭代字典。这可以通过多种方式完成,我们将展示一个示例。这同样来自 RestDirector 的诊断,其中渲染了应用程序中的程序集列表。
在这种情况下,字典是基于哈希的,因此其顺序是为了内部查找性能,而不是人类性能。因此,在稍微牺牲了排序以便渲染的开销后,我们将字典转换为 SortedList 进行渲染。幸运的是,代码很简单。
public void XmlRenderElements(DirectorWare.File.XmlFile file)
{
SortedList<string, Assembly> list
= new SortedList<string, Assembly>(this.dictionary);
foreach (Assembly assembly in list.Values)
file.RenderElement("Assembly", assembly);
}
我们可以保持如此简单,因为我们字典的键值也包含在值对象中。如果不是这样,您可以通过使用前面显示的 XmlFileMethod 委托将键作为属性添加到正在渲染的元素中。
解析
解析使用相似但互补的方法。每个属性都通过调用类的 XmlParseAttribute 方法来解析,每个元素都通过 XmlParseElement 来解析。两种方法的工作方式相似,只是数据不同。在这两种情况下,XmlFile 都包含一个名为 Name 的字段,其中包含当前正在解析的节点。所以回到我们的第一个 XML 示例
<Element1 Attribute1="Value1" Attribute2="123">
<Element2>Element2 的数据</Element2>
<Element3>Element3 的数据</Element3>
</Element1>
属性的解析首先发生,并且按出现顺序进行。通常,您会检查“file.Name”的值并采取相应行动。在大多数情况下,通过一系列的比较并匹配时返回,而不是使用“else if”语句,是最容易阅读的。这允许轻松地重新排序或注释掉。属性值可以以其原始形式返回,也可以转换为如下所示的 long。
public void XmlParseAttribute(DirectorWare.File.XmlFile file)
{
if (file.Name == "Attribute1")
{ this.attribute1 = file.ParseAttributeString; return; }
if (file.Name == "Attribute2")
{ this.attribute2 = file.ParseAttributeLong; return; }
}
元素的解析概念上是相同的,但也处理嵌套元素。在下面的“Element2”情况下,我们期望一个简单的文本元素,所以我们可以取元素的文本并返回。更典型的情况是,元素通过解析新对象来处理。我们将“Element3”显示为一个被实例化然后被解析的对象。
public void XmlParseElement(DirectorWare.File.XmlFile file)
{
if (file.Name == "Element2")
{ this.element2 = file.ParseElementString; return; }
if (file.Name == "Element3")
{ file.Parse(this.element3 = new Element3()); return; }
}
解析类内的嵌套元素
先前我们从单个类中渲染了以下内容:
<Environment>
<OS Platform="Win32NT" Version="6.0.6001.65536"/>
</Environment>
要解析 Environment 类,我们可以这样做:
public void XmlParseElement(DirectorWare.File.XmlFile file)
{
if (file.Name == "OS")
{ file.Parse(this.xmlParseAttributeOS, null); return; }
}
private void xmlParseAttributeOS(DirectorWare.File.XmlFile file)
{
if (file.Name == "Platform")
{ this.osPlatform = file.ParseAttributeString; return; }
if (file.Name == "Version")
{ this.osVersion = file.ParseAttributeString; return; }
}
我们所拥有的是类似于嵌套渲染,同样使用了基于 XmlFileMethod 委托的方法。我们找到一个名为“OS”的元素,然后调用“Parse”方法来解析该元素。我们向它传递两个方法:一个用于解析属性,另一个为 null,因为我们不解析任何元素。xmlParseAttributeOS 方法将处理我们期望的属性并将它们放入当前实例。
解析列表
我们可以像这样解析堆栈跟踪示例中的列表:
public void XmlParseElement(DirectorWare.File.XmlFile file)
{
if (file.Name == "Stack")
{
ExceptionStackTrace stackTrace = new ExceptionStackTrace();
file.Parse(stackTrace);
this.stackTraceList.Add(stackTrace);
return;
}
}
当一个“Stack”元素到达时,我们实例化一个新的 ExceptionStackTrace 类,并让它解析该元素。然后,我们将该填充的实例添加到我们类中的列表中。
解析字典
与解析一样,由于我们对代码拥有完全的控制权,因此我们能够对数据进行其他处理。现在我们可以根据需要处理字典。在我们的渲染示例中,我们将基于哈希的字典转换为 SortedList 进行渲染。由于我们不关心数据如何进入字典,因此只需将值放入字典中即可。
“Assembly”类包含键,因此我们可以解析 Assembly 并使用实例本身来检索键。我们使用赋值语句而不是字典的“Add”方法,以容忍相同的键。
public void XmlParseElement(DirectorWare.File.XmlFile file)
{
if (file.Name == "Assembly")
{
Assembly assembly = new Assembly();
file.Parse(assembly);
this.dictionary[assembly.Name] = assembly;
return;
}
}
处理根节点
既然我们可以处理内部节点,我们就必须处理根节点。存在将数据序列化到流或文件的各种方法。
根节点通过“RenderToFile”方法渲染到文件,或通过“RenderToStream”方法渲染到流。
file.RenderToFile("c:/File.xml", this);
我们传递路径或流,以及要渲染的 IXml 对象。我们使用与之前相同的方法和技巧,但有一个要求:渲染根节点时只能有一个根元素。由于我们使用方法调用而不是装饰,因此我们可以动态决定包括根名称在内的一切。
public void XmlRenderElements(DirectorWare.File.XmlFile file)
{
file.RenderElement("Root", this.xmlRenderRoot);
}
解析同样简单,有“ParseFromFile”和“ParseFromStream”方法。然而,我们现在有了额外的灵活性,因为我们正在使用相同的技术来解析根名称。这允许我们动态处理文件,具体取决于根名称。例如:
public void XmlParseElement(DirectorWare.File.XmlFile file)
{
if (file.Name == "RootA")
{ file.Parse(null, this.xmlParseRootA); return; }
if (file.Name == "RootB")
{ file.Parse(null, this.xmlParseRootB); return; }
}
未来的增强
RestDirector 的 XML 序列化正在考虑几项增强功能。有些是简单的辅助方法,可以使开发人员的生活更轻松。
例如,当应用程序的第二版添加了一些新节点时,读取旧节点应该不会有问题。当第一版读取由第二版写入的文件时,新节点将被忽略。但是,如果第一版现在保存更新后的文件,则第二版节点会丢失。解决方案是将未识别的节点保存为字符串并像这样渲染它们。这可以在您的代码中完成,但如果您可以简单地要求 RestDirector 为您完成此操作,那就更好了。
简单来说,RestDirector XML 序列化的第一个迭代在解析期间使用“System.Xml.XmlDocument”,但在渲染时则不使用。这增加了不必要的效率低下,应该被删除。在这方面,正确性优先,性能其次。
RestDirector 提供了显著的性能提升和灵活性,但对于简单的渲染来说,仍然希望至少有一些自动生成的代码。这也可以为至少半自动的模式生成敞开大门。目前正在审查这方面的选项。
结论
我们已经证明,使用 RestDirector 进行 XML 序列化可以根据需要变得简单或复杂,并且可以产生更易读的结果,具有更高的性能,并且序列化程序集生成没有启动时间。其结果可以提高应用程序性能和用户满意度。
RestDirector 的序列化不是一个“包罗万象”的解决方案,但它将处理大多数应用程序,在这些应用程序中,简洁性和性能至关重要。
RestDirector™ 的完整信息和示例应用程序可在 http://RestDirector.com 上找到。