使用 VTD-XML 编程 XPath






3.93/5 (8投票s)
了解如何利用 VTD-XML 的无状态 XPath 功能来实现无与伦比的效率和灵活性。
引言
XPath 最初作为 W3C 的 XSLT 和 XPointer 的一种元素,如今作为开发 XML 应用的有效工具,其受欢迎程度日益增长。XPath 的一个显著优点是它的简洁性:您无需手动导航分层数据结构,而是可以使用简洁的、类似“文件系统”的表达式来定位 XML 文档中的任何节点。然而,现有的大多数 XPath 引擎都处理 DOM 树或类似的面向对象模型,这些模型构建和修改缓慢,并且消耗大量内存。这给希望利用 XPath 进行 SOA 应用开发的人带来了两难:要么(1)对性能敏感,要么(2)经常处理大型 XML 文档。
我最近的几篇关于 VTD-XML (http://vtd-xml.sourceforge.net) 的文章将其描述为新一代 XML 处理模型,在性能、内存使用和易用性方面超越了 DOM 和 SAX。VTD-XML 同时具有:
- 内存高效:VTD-XML 通常消耗的内存量介于 XML 文档大小的 1.3 到 1.5 倍之间,这意味着它非常适合处理大型 XML 文档。
- 高性能:VTD-XML 通常比 DOM 解析器快 5 到 10 倍,并且通常比具有空内容处理器的 SAX 解析器快约 100%。
- 易于使用:使用 VTD-XML 编写的应用程序比使用 DOM 或 SAX 编写的应用程序更简洁、更易读。
VTD-XML 的“秘密武器”是什么?与传统的 XML 解析器在解析的第一步创建基于字符串的标记不同,VTD-XML 内部使用线性缓冲区存储 64 位整数,这些整数包含 XML 标记的起始偏移量和长度,同时保持 XML 文档的完整性和未解码状态。VTD-XML 的所有优点都或多或少地源于这种“非提取式”标记化。在 API 层面,VTD-XML 由以下核心类组成:
- VTDGen 是封装主要解析、索引写入和索引加载功能的类。
- VTDNav 导出一个基于光标的 API,其中包含导航 XML 分层结构的方法。
- AutoPilot 是允许进行文档顺序元素遍历的类,类似于 Xerces 的 NodeIterator。
理解 VTD-XML 的 XPath 功能
VTD-XML 的 XPath 实现支持完整的 W3C XPath 1.0 规范,自 1.0 版本起引入。它建立在 VTDNav 基于光标导航的概念之上。AutoPilot 是导出所有与 XPath 相关的方法的主要类。正如早期文章中所述,要手动导航 XML 文档的分层结构,您需要获取 VTDNav 实例并反复调用“toElement()”将光标移动到文档的各个部分。使用 XPath,您实际上是要求 AutoPilot *自动*将 VTDNav 的光标移动到文档中所有符合条件的节点。 \
AutoPilot 的 XPath 相关方法包括:
declareXPathNameSpace(...)
:此方法将命名空间前缀(在 XPath 表达式中使用)绑定到一个 URL。selectXPath(...):
此方法将 XPath 表达式编译成内部表示。evalXPath(...):
此方法将光标移动到节点集中的符合条件的节点。evalXPathToBoolean(...), evalXPathToNumber(...), evalXpathToString(...):
这三个方法分别将 XPath 表达式求值为布尔值、双精度数和字符串。resetXPath():
重置内部状态,以便 XPath 可以被重复使用。getExprString():
调用此方法来验证已编译表达式的正确性。
此外,还为 XPath 引入了另外两个异常类:
XPathParseException
:当 XPath 表达式中存在语法错误时抛出。XPathEvalException
:当 XPath 求值期间出现异常情况时抛出。
VTD-XML 的 XPath 实现最重要的创新是其无状态、非阻塞的节点集求值。与其他 XPath 引擎(例如XmlDocuemnt、XPathDocument JAXEN、XALAN等)一次性返回整个节点集不同,VTD-XML 在 XPath 引擎找到匹配项后立即返回符合条件的节点。关键方法是AutoPilot的evalXPath()
,每次调用时,它会将光标移动到下一个节点(如果节点集中至少还有一个剩余)。为了说明这两种 XPath 求值风格之间的差异,让我们看两个分别用JAXEN和VTD-XML编写的简短代码片段,它们对“catalog.xml”执行相同的应用程序逻辑,如下所示。
<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</CD>
<CD>
<TITLE>Still got the blues</TITLE>
<ARTIST>Gary More</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>Virgin redords</COMPANY>
<PRICE>10.20</PRICE>
<YEAR>1990</YEAR>
</CD>
<CD>
<TITLE>Hide your heart</TITLE>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR>
</CD>
<CD>
<TITLE>Greatest Hits</TITLE>
<ARTIST>Dolly Parton</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>RCA</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1982</YEAR>
</CD>
</CATALOG>
基于 XPathDocument 的代码如下所示。
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
namespace app0
{
class Program
{
static void Main(string[] args)
{
XPathDocument myXPathDocument = new XPathDocument(args[0]);
XPathNavigator myXPathNavigator = myXPathDocument.CreateNavigator();
XPathExpression myXPathExpr = myXPathNavigator.Compile(
"/CATALOG/CD/TITLE/text()");
XPathNodeIterator myXPathNodeIterator = myXPathNavigator.Select(myXPathExpr);
// iterate and print out the nodes
while (myXPathNodeIterator.MoveNext())
Console.WriteLine(myXPathNodeIterator.Current.Value);
}
}
}
用 VTD-XML 编写的相同应用程序如下所示。
using System;
using System.Collections.Generic;
using System.Text;
using com.ximpleware;
namespace app1
{
class Program
{
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
int i;
AutoPilot ap = new AutoPilot();
ap.selectXPath("/CATALOG/CD/TITLE/text()");
if (vg.parseFile("catalog.xml", false))
{
VTDNav vn = vg.getNav();
ap.bind(vn);
//XPath eval returns one node at a time
while ((i = ap.evalXPath()) != -1)
{
Console.WriteLine(" text ==> " +
vn.toString(i));
}
ap.resetXPath();
}
}
}
}
上面显示的示例代码仅处理一个 XPath 表达式。如果您处理多个 XPath 表达式,您将需要实例化多个 AutoPilot 对象,每个对象对应一个不同的 XPath 表达式。如果您多次调用 selectXPath(...),AutoPilot 对象只记住最后一个 XPath 表达式。一旦 AutoPilot 对象选择了 XPath 表达式,您就可以调用 evalXPath(),它会将嵌入的 VTDNav 实例的光标移动到下一个可用的节点,并返回其 VTD 记录索引。如果节点集中不再有节点,evalXPath() 将返回 -1。
如果您想在多个 XML 文档上执行相同的 XPath 查询怎么办?在这种情况下,关键是将 XPath 重用与 AutoPilot 的延迟绑定结合起来。在对一个 XML 文档执行查询后,您需要先通过调用 bind(...) 将相同的 AutoPilot 对象绑定到对应于不同 XML 文档的新 VTDNav 对象。在评估 XPath 之后,您还需要调用 resetXPath(),它会重置 XPath 的内部状态,使其准备好重用。
以下示例展示了如何在两个单独的 XML 文档上应用两个 XPath 表达式。
using System;
using System.Collections.Generic;
using System.Text;
using com.ximpleware;
namespace app2
{
class test {
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
int i;
AutoPilot ap = new AutoPilot();
ap.selectXPath("/CATALOG/CD/TITLE/text()");
AutoPilot ap2 = new AutoPilot();
ap2.selectXPath("/CATALOG/CD[PRICE < 10]/TITLE/text()");
// apply both XPath expressions to "catalog.xml"
if (vg.parseFile("catalog.xml", false))
{
VTDNav vn = vg.getNav();
ap.bind(vn);
//XPath eval returns one node at a time
while ((i = ap.evalXPath()) != -1)
{
Console.WriteLine(" text ==> " +
vn.toString(i));
}
ap.resetXPath();
ap2.bind(vn);
while ((i = ap2.evalXPath()) != -1)
{
Console.WriteLine(" text ==> " +
vn.toString(i));
}
ap2.resetXPath();
}
// apply both XPath expressions to a different file
if (vg.parseFile("another.xml", false))
{
VTDNav vn = vg.getNav();
ap.bind(vn);
//XPath eval returns one node at a time
while ((i = ap.evalXPath()) != -1)
{
Console.WriteLine(" text ==> " +
vn.toString(i));
}
ap.resetXPath();
ap2.bind(vn);
while ((i = ap2.evalXPath()) != -1)
{
Console.WriteLine(" text ==> " +
vn.toString(i));
}
ap2.resetXPath();
}
}
}
}
将 XPath 求值为字符串、数字和布尔值
XPath 1.0 还定义了一个表达式可以求值为字符串、双精度数或布尔值。VTD-XML 的 AutoPilot 类包含evalXPathToString()
、evalXPathToNumber()
和evalXPathToBoolean()
。请注意,这些方法不需要 resetXPath() 即可重用,因为它们会在内部执行重置。
优点
与其它 XPath 引擎相比,VTD-XML 的非阻塞 XPath 求值方法具有诸多优点。在不牺牲 XPath 任何固有的优点的同时,VTD-XML 提供了无与伦比的灵活性,并将更多控制权交给了您。考虑这样一个用例:您想使用 XPath 处理一个大型 XML 文档,而节点集包含成千上万个节点。使用其他 XPath 引擎,您的应用程序逻辑必须等到整个节点集返回后才能执行任何操作。使用 VTD-XML,您的应用程序逻辑可以在第一个节点返回后立即开始。如果您的应用程序只处理节点集中的少数节点,或者您对 XML 文档有先验知识(例如,您知道在一个大型 XML 文件的开头只有一个匹配节点),为什么还要等到 XPath 完成对整个文档的搜索呢?在这种情况下,其他 XPath 引擎的节点求值不必要地阻塞了程序流程,似乎更像是错误的方法!在其他用例中,VTD-XML 的非阻塞 XPath 甚至可以用来遍历节点,这是其他类型的 XPath 引擎无法实现的。
当与 VTD-XML 的其他优点/功能结合时,与 DOM 和 SAX 的比较将迅速变得一边倒。无论文件大小如何,无论是解析、更新还是 XPath 求值(稍后展示),VTD-XML 都提供了许多人以前认为不可能实现的全面性能改进。作为处理小型到中型 XML 文件的事务性 SOA 应用程序的首选处理模型,VTD-XML 将轻松超越 DOM。对于大型 XML 文件,好处只会更大。您将从冗长、丑陋且难以维护的 SAX 代码转向简洁、短小的 VTD-XML 代码,同时享受 VTD-XML 随机访问的全部功能,并获得 10 到 50 倍的速度提升。如果您经常处理大型 XML 文档或返回大量节点集的 XPath 表达式,VTD-XML 是必不可少的!
常见用法模式
要基于 VTD-XML 构建应用程序,可以遵循一些常见模式。
将 evalXPath(..) 嵌入 while 的控制语句中
最常见的模式是将 `evalXPath()` 嵌入 while 循环的控制语句中。到目前为止,所有代码都使用此模式来导航到文档中的符合条件的节点。`EvalXPath()` 的返回值(如果等于 -1)表示节点集中没有剩余节点。否则,返回值等于 XPath 选择的节点的 VTD 记录索引,而(绑定到 AutoPilot 对象的)VTDNav 对象的光标也指向该节点。以下两个简短的代码片段完全等效。
int i;
ap.bind(vn);
ap.selectXPath(...);
while( (i=ap.evalXPath())!=-1){
}
int i;
ap.bind(vn);
ap.selectXPath(...);
while(ap.evalXPath()!>=0){
i = vn.getCurrentIndex();
}
与手动导航结合
但有时您希望在 while 循环中进一步导航光标。要做到这一点,规则是嵌套在循环中的代码段不得更改节点位置。您可以将光标移回原始位置,或者使用 VTDNav 对象的 `push()` 和 `pop()` 来强制执行此规则。对于上面显示的相同 XML 数据,第一个示例在 while 循环中执行一些手动导航,在下一次迭代之前将光标移回起始位置。
using System;
using System.Collections.Generic;
using System.Text;
using com.ximpleware;
namespace app3
{
class Program
{
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
int i;
AutoPilot ap = new AutoPilot();
ap.selectXPath("/CATALOG/CD[PRICE < 10]");
if (vg.parseFile("catalog.xml", false))
{
VTDNav vn = vg.getNav();
ap.bind(vn);
//XPath eval returns one node at a time
while ((i = ap.evalXPath()) != -1)
{// get to the first child
if (vn.toElement(VTDNav.FIRST_CHILD, "TITLE"))
{
int j = vn.getText();
if (j != -1)
Console.WriteLine(" text node ==>" + vn.toString(j));
vn.toElement(VTDNav.PARENT); // move the cursor back
}
}
ap.resetXPath();
}
}
}
}
第二个示例使用 `push()` 和 `pop()`,效率稍低。
using System;
using System.Collections.Generic;
using System.Text;
using com.ximpleware;
namespace app4
{
class Program
{
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
int i;
AutoPilot ap = new AutoPilot();
ap.selectXPath("/CATALOG/CD[PRICE < 10]");
if (vg.parseFile("catalog.xml", false))
{
VTDNav vn = vg.getNav();
ap.bind(vn);
//XPath eval returns one node at a time
while ((i = ap.evalXPath()) != -1)
{
vn.push();
// get to the first child
if (vn.toElement(VTDNav.FIRST_CHILD, "TITLE"))
{
int j = vn.getText();
if (j != -1)
Console.WriteLine(" text node ==>" + vn.toString(j));
}
vn.pop();
}
ap.resetXPath();
}
}
}
}
嵌套 XPath 支持
如果您想执行的导航更复杂,您实际上可以嵌套 XPath 查询。上面的代码用 XPath 重写如下。确保光标位置一致性的规则仍然适用。
using System;
using System.Collections.Generic;
using System.Text;
using com.ximpleware;
namespace app6
{
class Program
{
static void Main(string[] args)
{
VTDGen vg = new VTDGen();
int i;
AutoPilot ap = new AutoPilot();
ap.selectXPath("/CATALOG/CD[PRICE < 10]");
AutoPilot ap2 = new AutoPilot();
ap2.selectXPath("TITLE/text()");
if (vg.parseFile("catalog.xml", false))
{
VTDNav vn = vg.getNav();
ap.bind(vn);
ap2.bind(vn);
//XPath eval returns one node at a time
while ((i = ap.evalXPath()) != -1)
{
vn.push();
// get to the first child
int j;
while ((j = ap2.evalXPath()) != -1)
{
Console.WriteLine(" text node ==>" + vn.toString(j));
}
// doesn't forget this since the next iteration will reuse
// ap2's XPath!!!
ap2.resetXPath();
vn.pop();
}
ap.resetXPath();
}
}
}
}
临时树遍历模式
当然,没有什么能阻止您仅调用 `evalXPath()` 来将光标移动到 XML 文档中的所需目标。
import com.ximpleware.*;
public class vtdXPath5{
public static void main(String args[]) throws Exception{
VTDGen vg =new VTDGen();
int i;
AutoPilot ap = new AutoPilot();
ap.selectXPath("/CATALOG/CD[PRICE < 10]");
if (vg.parseFile("catalog.xml", false)){
VTDNav vn = vg.getNav();
ap.bind(vn);
//XPath eval returns one node at a time
if (ap.evalXPath()>=0){
// do what ever you like here with vn
}
}
}
}
XPath 求值性能基准测试
本节将展示 VTD-XML 的 XPath 求值性能,并将其与 Xalan 和 `JAXEN` 进行比较,这两者都与 Xerces DOM 配合使用。基准测试程序预编译一组 XPath 表达式,然后对同一组文档(选择了三种结构相似但大小不同的文档)重复执行查询。下表和图表报告了查询执行的平均延迟。有关测试设置、方法和代码的完整描述和讨论,请访问 Latency Number
/*/*/*[position() mod 2 = 0]
Jaxen (ms) | JDK (ms) | VTD-XML (ms) | |
po_small.xml | 0.0395 | 1.123 | 0.00994 |
po_medium.xml | 0.524 | 6.552 | 0.115 |
po_big.xml | 8.812 | 94.535 | 1.292 |
/purchaseOrder/items/item[USPrice <100]
Jaxen (ms) | JDK (ms) | VTD-XML (ms) | |
po_small.xml | 0.0857 | 1.204 | 0.048 |
po_medium.xml | 1.498 | 9.381 | 0.854 |
po_big.xml | 22.262 | 113.831 | 9.238 |
/*/*/*/quantity/text()
|
Jaxen (ms) | JDK (ms) | VTD-XML (ms) |
po_small.xml | 0.107 | 1.0756 | 0.0317 |
po_medium.xml | 1.922 | 7.0268 | 0.537 |
po_big.xml | 32.181 | 106.7 | 6.339 |
//item/comment
Jaxen (ms) | JDK (ms) | VTD-XML (ms) | |
po_small.xml | 0.356 | 1.208 | 0.089 |
po_medium.xml | 9.824 | 9.922 | 1.505 |
po_big.xml | 197.319 | 138.45 | 17.825 |
//item/comment/../quantity
Jaxen (ms) | JDK (ms) | VTD-XML (ms) | |
po_small.xml | 0.374 | 1.274 | 0.105 |
po_medium.xml | 11.147 | 10.298 | 1.787 |
po_big.xml | 204.714 | 140.06 | 21.06 |
/*/*/*/* [position() mod 2 = 0]
Jaxen (ms) | JDK(ms) | VTD-XML(ms) | |
po_small.xml | 0.136 | 1.21 | 0.0353 |
po_medium.xml | 2.7 | 8.216 | 0.604 |
po_big.xml | 65.469 | 122.83 | 6.559 |
/purchaseOrder/items/item[USPrice > 100]/comment/text()
Jaxen (ms) | JDK (ms) | VTD-XML (ms) | |
po_small.xml | 0.113 | 1.262 | .06 |
po_medium.xml | 2.082 | 9.154 | 1.061 |
po_big.xml | 32.892 | 121.07 | 12.35 |
图形视图
/*/*/*[position() mod 2 = 0]
/purchaseOrder/items/item[USPrice <100]
/*/*/*/quantity/text()
//item/comment
//item/comment/../quantity
/*/*/*/* [position() mod 2 = 0]
/purchaseOrder/items/item[USPrice > 100]/comment/text()
结论
希望本文为您提供了 VTD-XML 的 XPath 实现的介绍和概述。结合其他固有的优点,VTD-XML 使您能够编写简洁、易读的代码,并实现无与伦比的性能、效率和灵活性。因此,关于 XML 编程的一些根深蒂固的观念和假设即将改变。由此,建立在 XML 之上的基础设施,例如 Web 服务和 SOA,也将经历积极的变革。我的下一篇文章将向您展示如何使用 VTD-XML 显著改进 Java-XML 数据绑定。
历史
- 7/29/08 初始文章发布
- 8/11/08 添加了涵盖
EvalXPathToString()
、EvalXPathToNumber()
、EvalXPathToBoolean()
的子节。 - 6/9/10 文章编辑