大型 XML 文件处理和索引






4.88/5 (8投票s)
为大型 XML 文件编制索引以实现快速访问。使用 IO 和 XMLReader 进行正则表达式解析。
引言
XML 文档如果相对较小(小于 10-20 MB)则性能表现良好。DOM 会将内容加载到内存中进行验证等操作……但是对于大型文档怎么办呢?
更大的文档会占用更多内存,消耗更多资源,性能会受到严重影响。像 XML Spy 这样的工具会尝试将文件加载到内存并进行验证,最后……会卡住并耗尽内存。(测试的文件超过 250 MB)。其他 XML 解析器则会默默崩溃。
背景
你可能遇到了类似的问题,否则你也不会阅读这篇文章。
这里有一个简单的解决方案,可以从大型到巨型的 XML 文档中获取感兴趣的节点或所有节点,而几乎不会有性能损失(好吧,是很小的损失)并且几乎不会占用内存或 CPU。
我们这样做,将我们感兴趣的节点索引到内存中,或者在本例中索引到另一个索引文件中(* .xml.idx),并将输出节点写入另一个文件中(* .xml.sorted)。现在我们可以使用索引来快速访问任何感兴趣的节点。生成条件和选择方法由你决定。
这是一个非常通用的处理客户发票的示例。测试文件超过 420MB,平均需要 40-50 秒来处理并写入另一个包含所有感兴趣节点的文档。处理时间将取决于你的处理器和机器内存量。
关键元素:FileStream
、XMLReader.ReadSubtree
和 Regex
。
Using the Code
操作方法
-
获取源代码并编译项目。它是用 Visual Studio 2005 构建的,但在 Visual Studio 2003 和 Visual Studio 2008 中也能工作。
-
创建一个包含
<INVOICE ID=”123”…..>
元素的 XML 文档。参见图 6 的示例。a. 如果你有自己的大型 XML 文档想尝试,那么在“要索引的节点”框中输入节点名称,并指定你想要获取的值。在我的例子中,我关心 INVOICE ID,所以我的条件将被评估为"//./@ID" – 当前元素的 ID
属性。请用你自己的文档来选择适合你的测试。图 1 -
点击“索引 XML 文件”以获取你的大型 XML 文档副本(图 2)。它将被复制到你的 Debug 文件夹所在目录,或应用程序文件夹,具体取决于你的运行方式。嗯,你知道的……。
图 2
private void MakeCopy() { string curDir = Application.StartupPath + "/"; FileInfo fi = new FileInfo(filePath); wokingCopy = curDir + (Guid.NewGuid()) + "_" + fi.Name; this.Text = "File size is:" + fi.Length/1000000 + " - MB"; File.Copy(filePath, wokingCopy, true); xmlIndexFile = wokingCopy + ".idx"; this.Text += " Copied to:" + curDir; using (StreamWriter f = File.CreateText(xmlIndexFile)) { f.Close(); } sw = new StreamWriter(xmlIndexFile, true, Encoding.UTF8); }
将文件复制到新目录后,文件大小将在表单文本字段(图 3)中显示。
开始解析过程,并写入 *.idx 和 *.sorted 文件。
使用 Regex 查找行中的匹配项。
using (FileStream fs = new FileStream(wokingCopy, FileMode.Open, FileAccess.Read))
using (StreamReader sr = new StreamReader(wokingCopy, Encoding.UTF8)) {
string parseText = txtNode.Text.Trim();
//Matching expression for the node:
Regex rx = new Regex(@"<" + parseText, RegexOptions.Compiled | RegexOptions.IgnoreCase);
int pos = 0;
int startIndex = 0;
int lastPositio = 0;
//Read each line in XML document as regular file stream.
do {
string line = sr.ReadLine();
pos += Encoding.UTF8.GetByteCount(line) + 2;// 2 extra bites for end of line chars.
MatchCollection m = rx.Matches(line);
foreach (Match mt in m) {
startIndex = lastPositio + mt.Index;
ValidateXPathCondition(fs, startIndex);
}
lastPositio = pos;
} while (!sr.EndOfStream);
sr.Close();
sw.Close();
fs.Close();
}
WriteSortedDocument();
最后,你的处理时间(秒)将显示在表单文本(图 4)中。
这是如何读取流并存储信息的方法。
private void WriteSortedDocument() {
using(FileStream fs = new FileStream(wokingCopy, FileMode.Open, FileAccess.Read))
using (StreamWriter wr = new StreamWriter(wokingCopy + ".sorted", false)) {
wr.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
wr.WriteLine("<" + txtNode.Text.Trim().ToUpper() + "S>");
foreach (int key in indexList.Keys) {
fs.Seek(indexList[key], SeekOrigin.Begin);
using (XmlReader reader = XmlReader.Create(fs)) {
reader.MoveToContent();
XmlDocument d = new XmlDocument();
d.Load(reader.ReadSubtree());
wr.WriteLine(d.InnerXml);
wr.Flush();
reader.Close();
}
}
fs.Close();
wr.WriteLine("</" + txtNode.Text.Trim().ToUpper() + "S>");
wr.Flush();
wr.Close();
}
}
/// <summary>
/// Write value and index in original file into the indexing file.
/// </summary>
/// <param name="value"></param>
/// <param name="startIndex"></param>
private void SaveMatchedIndex(string value, int startIndex) {
sw.WriteLine(value + "\t" + startIndex);
sw.Flush();
//Add new element to sorted list. Sorting is by key
indexList.Add(Int32.Parse(value), startIndex);
}
新生成的文件

原始 XML 文件片段
索引文件 *.xml.idx 的一部分
已排序的 *.sorted XML 文件的一部分:文件按发票 ID 排序。
测试你的索引。
转到 *.xml.idx 文件,选择一个索引(这是第二个选项卡),然后将该值输入到复制文件中的位置。感兴趣的节点将立即显示。
点击“读取”按钮测试索引位置 311383:它将返回选定的节点。参见本文顶部的图 9。
关注点
如果您有任何评论或疑问,请给我发电子邮件。
还有一个很棒的项目 http://vtd-xml.sourceforge.net/ 用于索引大型 XML 文档,但它也有其优缺点。
历史
- 创建于 2008 年 11 月 14 日