使用 VTD+XML 索引 XML 文档






4.68/5 (11投票s)
介绍了一个简单、高效、人类可读的 XML 索引,名为 VTD+XML。
摘要
传统上,基于 DOM 或 SAX 的企业应用程序在多次访问同一文档时必须重复 CPU 密集型的 XML 解析。本文介绍了一种非常简单、通用的本地 XML 索引,名为 VTD+XML,它消除了这些应用程序重复解析的需要。
使用 VTD+XML 避免重复 XML 解析
正如在“使用 VTD-XML 简化 XML 处理”中所讨论的,迄今为止,XML 应用程序开发的一个基本假设是,在对 XML 文档进行任何操作之前必须对其进行解析。换句话说,XML 应用程序的处理逻辑在没有解析的情况下无法启动。XML 解析通常比 XPath 评估等其他 XML 操作慢得多,并且经常被视为对数据库性能的威胁。当这些应用程序对不经常更改的 XML 数据执行多次只读访问时,能否消除相关的重复解析的开销岂不更好?
通过 VTD-XML 的本地 XML 索引功能,您可以精确地做到这一点。封装各种解析例程的类 VTDGen
现在添加了“readIndex(...)
”和“writeIndex(...)
”。有两个与索引相关的异常:indexWriteException
和 indexReadException
。
让我将这些新方法付诸实践,并向您展示如何在应用程序中启用索引功能。考虑以下 XML 文档:
<purchaseOrder orderDate="1999-10-21">
<item partNum="872-AA">
<productName>Lawnmower</productName>
<quantity>1</quantity>
<USPrice>148.95</USPrice>
</item>
</purchaseOrder>
下面是一个名为“printPrice.cs”的简单应用程序,它打印元素“USPrice
”的内容。请注意,它解析 XML 文件,然后使用 XPath 过滤目标节点。
using com.ximpleware.*;
public class printPrice{
public static void main(String args[]){
VTDGen vg = new VTDGen();
try{
if (vg.parseFile("po.xml",true)){
VTDNav vn = vg.getNav();
AutoPilot ap = new AutoPilot(vn);
ap.selectXPath("/purchaseOrder/item/USPrice/text()");
int i=-1;
while((i=ap.evalXPath())!=-1){
System.out.println(" USPrice ==> "+vn.toString(i));
}
}
}catch(Exception e){
}
}
}
需要对 C# 代码进行一些更改,以添加 VTD-XML 的新索引功能。首先,您需要读取 XML 文档,对其进行解析,然后写出同一 XML 文档的索引版本。从那时起,您的应用程序就可以直接在索引之上运行 XPath 查询或处理逻辑,从而节省了再次解析 XML 文档的 CPU 周期。以下代码片段(分别命名为“genIndex.cs”和“accessIndex.cs”)展示了如何生成和访问索引。请注意,按顺序执行时,这两个应用程序都产生与“printPrice.cs”相同的输出。
第一个应用程序 (genIndex.cs) 读取“po.xml”并生成“po.vxl”。
using System;
using com.ximpleware;
namespace genIndex
{
class genIndex
{
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
try
{
if (vg.parseFile("d:/codeProject/app3/po.xml", true))
{
vg.writeIndex("d:/codeProject/app3/po.vxl");
}
}
catch (VTDException e)
{
}
}
}
}
第二个应用程序 (accessIndex.java) 加载“po.vxl”并使用 XPath 表达式“/purchaseOrder/item/USPrice/text()
”过滤文档。
using System;
using com.ximpleware;
namespace accessIndex
{
class accessIndex
{
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
try
{
VTDNav vn = vg.loadIndex("d:/codeProject/app3/po.vxl");
AutoPilot ap = new AutoPilot(vn);
ap.selectXPath("/purchaseOrder/item/USPrice/text()");
int i = -1;
while ((i = ap.evalXPath()) != -1)
{
Console.WriteLine(" USPrice ==> " + vn.toString(i));
}
}
catch (VTDException e)
{
}
}
}
}
VTD+XML 30 秒上手
通过将 XML 解析与应用程序逻辑解耦,上面示例的关键是索引文件“po.vxl”,该文件符合 VTD+XML 规范。什么是 VTD+XML?由于 VTD-XML 对 XML 信息集的内部表示本质上是持久的,因此 VTD+XML 顾名思义,只是一种二进制打包格式,它将 VTD 记录、LC 条目和 XML 组合到一个文件中。详细的技术规范可以在此处找到。
一个简单示例
本节深入探讨规范的细节,通过逐字节手动组合 VTD+XML 索引。为简单起见,本示例选择索引一个简单的 XML 文档,该文档包含一个没有子元素的根元素,其解析表示没有位置缓存条目。此示例还假定小端字节序(如 C# 中)和 UTF-8 文档编码(默认字符集)。命名空间感知设置为 false。
相应索引文件的前四个字节字是 0x01028000,包含:
- 第一个字节中的 VTD+XML 版本号 (0x01)
- 第二个字节中的字符编码格式 (0x02)
- 命名空间感知、LC 条目在最后一级的字长、平台的字节序以及 VTD 版本,均在第三个字节 (0x80) 的各个位字段中编码。
- 文档深度(0x00,因为根元素没有子元素)
第二个四个字节字的值为 0x00040001,包含:
- 上半部分 16 位中的 VTD-XML 实现支持的 LC 级别数(大端字节序中的 0x0004)
- 下半部分 16 位中的根元素索引值(大端字节序中的 0x0001)
接下来的四个四个字节字保留为零。
所有后续 32 位或 64 位字的字节序都取决于平台,并在 VTD+XML 规范的第三个字节中指定。接下来的八字节字表示 XML 文档的大小(以字节为单位),在本例中等于七 (0x0700000000000000)。紧随其后 (0x3C726F6F742F3E00) 是 XML 的字节内容,通过在末尾填充零向上取整为八个字节的整数倍。
VTD+XML 索引的剩余部分由多个相邻的段组成,每个段包含一个八字节字(0x0000000000000002 表示 VTD 记录或 LC 条目数),后跟 VTD 记录或 LC 条目的实际内容。第一个八字节字 (0x020000000000000000) 表示有两个 VTD 记录,它们是 0x000000000000F0DF 和 0x0100000004000000。
其余三个八字节字的值均为零,表示第一、第二和第三级的查找缓存(location caches)在 VTD+XML 索引中没有条目。
作为最终输出,“
优点和局限性
由于 VTD+XML 直接组合了 VTD 和 XML,因此它继承了 VTD-XML 解析的所有优点。与现有的 XML 索引(例如各种纯二进制 XML 索引模型化标签化、有序树等)相比,VTD+XML 具有许多独特的技术优势:
- 通用性 - 在 VTD+XML 之前,大多数本地 XML 索引仅针对 XPath 查询的特定类型(例如,轴)进行了优化。如果输入查询与索引类型略有不同,则查询执行仍必须 resorting to expensive parsing. 由于这种限制,许多本地 XML 数据库现在要求用户创建多个索引,每个索引对应一种输入查询类型,以便用户可以从这些索引中获益。问题是,XML 数据库应用程序通常服务于许多不可预测且复杂的查询类型,这常常使索引的优势微不足道。相比之下,VTD+XML 是第一个完全消除了 XML 解析成本并可预测地加速任何类型 XPath 查询的索引。它也与命名空间(namespaces)配合得非常好。
- 人类可读 - VTD+XML 也是第一个人类可读的 XML 索引。您可以实际在文本编辑器中打开它来检查 XML 文本。图 2 显示了“po.vxl”在“VI”中的样子。VTD+XML 的人类可读性不仅仅是一个不错的特性,它还提供了比纯二进制索引方案的明显优势。在其他条件相同的情况下,将 XML 保留其原始格式可以避免与二进制格式相互转换的处理成本。此外,如果您的应用程序只想修改 XML 有效负载,例如从中插入从另一个 SOAP 消息中提取的 XML 片段,那么将 XML 转换为二进制格式有什么意义?在面向服务的异构环境中,以原始格式维护 XML 可以自动保留开放性和互操作性。在我看来,XML 的唯一无损等价物就是 XML 本身,不多不少。
- 卓越的索引性能 - 生成 VTD+XML 索引的性能与 VTD-XML 的解析性能相同,因为从两个不同角度来看,两者本质上是相同的操作。在 1.7GHz Pentium 机器上,可以预期持续的索引性能为 50 MB/s-70 MB/s。
- 易于使用 - 通常,在现有 VTD-XML 代码中添加几行(如前一个示例中所示的
loadIndex(...)
和writeIndex(...)
)即可在您的应用程序中启用 VTD+XML。 - 紧凑 - VTD+XML 的大小通常比相应 XML 文档的大小大 30%-50%。这同样符合 VTD-XML 处理模型的内存使用情况。
- 平台中立 - 就像 XML 一样,VTD+XML 被设计为平台中立的,因为它明确包含有关生成索引的平台的字节序(Endian-ness)的信息。C 或 C# 版 VTD-XML 代码的用户可以自动识别并利用 Java 版生成的索引。
同时,VTD+XML 的用户需要注意以下限制:
- 文档大小上限 - VTD+XML 支持的最大 XML 文档大小为 2GB(无命名空间支持)。有命名空间时,VTD+XML 支持的最大大小为 1GB。
- 不支持外部实体 - VTD-XML 目前支持 XML 1.0 中定义的五个内置实体引用(<、>、&、' 和 ")。
涉及 XML 内容更新的案例
有些人可能会问:如果后续的 XML 操作涉及改变偏移量的内容更新怎么办?一般来说,这些用例通常需要更新 XML 文档并重新索引。而且,对于大型 XML 文档,您可能会认为重新索引的成本相当可观。然而,实际上有一些变通方法,它们都旨在降低甚至消除重新索引的成本。
第一个变通方法
与其为单个大型 XML 文档创建 VTD+XML 索引,不如将 XML 文档拆分成多个较小的文档,每个文档都使用 VTD+XML 进行索引。从那时起,您只需要为那些“已更新”的 XML 片段重新生成 VTD+XML 索引,这些片段通常小得多,因此重新索引的成本也较低。
VTD+XML 的编辑功能允许您修改 XML 内容,而无需重新生成索引。下面的代码利用 VTDNav
类的新 overWrite(...)
方法将“<root>good</root>
”的文本节点从“good”更改为“bad”。如果新内容比旧内容短或等长,则“overWrite(...)
”方法会用空格填充非重叠部分,并返回 true。否则,原始内容不变,“overWrite(...)
”返回 false
。
using System;
using System.Text;
using com.ximpleware;
namespace template
{
class template
{
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
Encoding eg = System.Text.Encoding.GetEncoding("utf-8");
if (vg.parseFile("d:/codeProject/app3/temp1.xml", true))
{
VTDNav vn = vg.getNav();
int i = vn.getText();
//print "good"
Console.WriteLine("text ---> " + vn.toString(i));
if (vn.overWrite(i,eg.GetBytes("bad")))
{
//overwrite, if successful, returns true
//print "bad" here
Console.WriteLine("text ---> " + vn.toString(i));
}
}
}
}
}
尽管简单,但这种“编辑”功能实际上具有意想不到的性能影响。考虑数据库表设计,其中您指定了列宽。您现在可以将相同的技术应用于 XML 组合:通过预先将一些额外的空格序列化到文本节点或属性值中,您可以对这些节点进行“原地”更新,并且无需重新生成索引。您甚至可以在 XML 文档中预先序列化虚拟元素,这些元素包含文本节点或属性值,其初始值完全是空格。这些虚拟元素作为模板,以应对未来的内容更新,如下面的示例所示。
模板
<purchaseorder orderdate="" />
<purchaseorder orderdate="" />
<purchaseOrder orderDate=" ">
<item partNum=" " >
<productName> </productName>
<quantity> </quantity>
<USPrice> </USPrice>
</item>
</purchaseOrder>
<quantity /></quantity />
<usprice /> </usprice />
</purchaseorder />
</purchaseorder />
“嵌入”数据后
<purchaseOrder orderDate="1999-10-21">
<item partNum="872-AA" >
<productName>Lawnmower </productName>
<quantity>1 </quantity>
<USPrice> 100 </USPrice>
</item>
</purchaseOrder>
同样,XML 内容删除的概念也值得重新思考。与其物理删除 XML 元素,不如通过使其对应用程序“不可见”来禁用 XML 元素,以达到相同的目的。好处:您又避免了重新索引的需要。请注意,这有利于 XML 作为松散编码数据格式的优势。下面是一个将元素的“enable”属性值设置为使其“不可见”的示例。
以前
<purchaseorder orderdate="1999-10-21"" />
<purchaseOrder orderDate="1999-10-21">
<item partNum="872-AA" enable="1">
<productName>Lawnmower</productName>
<quantity>1</quantity>
<USPrice>148.95</USPrice>
</item>
</purchaseOrder>
</purchaseorder />
操作后
<purchaseOrder orderDate="1999-10-21">
<item partNum="872-AA" enable='0'>
<productName>Lawnmower</productName>
<quantity>1</quantity>
<USPrice>148.95</USPrice>
</item>
</purchaseOrder>
应用程序场景
有两种不同的观点可以理解 VTD+XML 作为解决实际问题的实用解决方案。第一种是本地 XML 索引的传统观点。或者,您可以将 VTD+XML 视为一种向后兼容 XML 的二进制数据格式。
本地 XML 索引
从这个角度来看,您只需将 VTD+XML 作为本地 XML 数据存储的基础,以满足 XML/SOA 应用程序的后端数据需求。通过将其作为 BLOB(二进制大对象)保存在更传统的数据库表中,您可以获得额外的功能,如并发、数据完整性和复制。VTD+XML 比尴尬的基于分片(shredding)的 XML 到关系数据映射要好得多,在纯 XML/SOA 环境中非常适用。拥有大量 XBRL(可扩展商业报告语言)文档或那些大型 GML(地理标记语言)文件?VTD+XML 应该为您提供前所未有的强大功能。
二进制增强 XML
VTD+XML 还自然地扩展了 XML 的核心功能,将处理效率提升到一个全新的水平。换句话说,作为一种线上传输格式(wire format),XML 现在拥有了一切:它不仅易于学习、人类可读、可互操作且设计为松散编码,而且在性能方面,它还远远领先于 CORBA、DCOM 和 RMI。应用于 XML 流水线(pipelining)时,VTD+XML 可能会消除流水线每个阶段的重复解析——这是现有 XML 流水线规范(例如 XProc 和 XML 流水线定义语言)都未解决的问题。如果您的 DOM 应用程序通过 ESB(企业服务总线)传输大型文档花费太长时间,那么每秒传输 100MB 的速度听起来如何?
性能比较
本节主要强调了索引加载的性能。对于 VTD+XML,性能数字是索引加载到内存然后运行单个 XPath 的延迟。对于 DOM,性能是 DOM 解析加上运行单个 XPath(Xalan 和 Jaxen)的延迟。结果已标准化,因此显示的是比率。可以看到,当启用索引加载时,性能提升可能非常显著。
结论
本文介绍了 VTD+XML 的最新索引功能以及最新的基准测试数据,展示了它实现的效率水平。在 VTD+XML 之前,使用 DOM 或 SAX 编写的 XML/SOA 应用程序会产生 XML 解析、XPath 评估和可选的内容更新的开销。这些开销占应用程序总 CPU 周期的 80%-90% 或更多的情况并不少见。VTD+XML 消除了这些开销,因为没有多少开销可以优化了。使用 VTD+XML 作为解析器可将 XML 解析开销降低 5 倍-10 倍。接下来,VTD+XML 的增量更新独特地消除了更新 XML 的往返开销。此外,本文展示了 VTD+XML 创新的非阻塞、无状态 XPath 引擎,其性能远超 Jaxen 和 Xalan。通过添加索引功能,XML 解析现在已成为“可选”的。换句话说,阻碍 SOA 成功的障碍已悄然消失。但这只是另一个起点。可能不难看出,如果 VTD+XML 坚持过度对象分配(excessive object allocation)如 DOM,那么它的任何好处都不会存在。在 XML 处理的上下文中,XML 信息集的纯面向对象建模(例如,字符串和节点对象)似乎从一开始就不太合适。与其他任何事物一样,面向对象也有其弱点。问题(例如,DOM 和 SAX 的问题)出现在当人们为了选择而选择面向对象,并且停止质疑其合理性时。对我来说,知道何时不使用对象与知道何时使用对象同等重要,甚至更重要。源于这些弱点、约束和限制,VTD+XML 努力成为解决这些问题的简单、合理的答案。