在 C# 代码中解析(大)XML 的最佳方法是啥?
Posted
技术标签:
【中文标题】在 C# 代码中解析(大)XML 的最佳方法是啥?【英文标题】:What is the best way to parse (big) XML in C# Code?在 C# 代码中解析(大)XML 的最佳方法是什么? 【发布时间】:2010-10-15 03:30:05 【问题描述】:我正在用 C# 编写一个 GIS 客户端工具,以从服务器检索基于 GML 的 XML 模式(下面的示例)中的“特征”。提取限制为 100,000 个特征。
我猜测最大的 extract.xml 可能会达到 150 兆字节左右,所以显然 DOM 解析器已经出局了或者——XmlReader 和一个手工制作的对象图。
或者我还没有考虑过更好的方法?像 XLINQ,或者 ????
请有人指导我吗?特别是关于任何给定方法的内存效率。如果不是,我将不得不对这两个解决方案进行“原型设计”并并排分析它们。
我在 .NET 中有点像生虾。任何指导将不胜感激。
谢谢你。基思。
示例 XML - 最多 100,000 个,每个功能最多 234,600 个坐标。
<feature featId="27168306" fType="vegetation" fTypeId="1129" fClass="vegetation" gType="Polygon" ID="0" cLockNr="51598" metadataId="51599" mdFileId="NRM/TIS/VEGETATION/9543_22_v3" dataScale="25000">
<MultiGeometry>
<geometryMember>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>153.505004,-27.42196 153.505044,-27.422015 153.503992 .... 172 coordinates omitted to save space ... 153.505004,-27.42196</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</geometryMember>
</MultiGeometry>
</feature>
【问题讨论】:
【参考方案1】:使用 XmlReader
解析大型 XML 文档。 XmlReader
提供对 XML 数据的快速、只转发、非缓存访问。 (Forward-only 意味着您可以从头到尾读取 XML 文件,但不能在文件中向后移动。)XmlReader
使用少量内存,相当于使用简单的 SAX 阅读器。
using (XmlReader myReader = XmlReader.Create(@"c:\data\coords.xml"))
while (myReader.Read())
// Process each node (myReader.Value) here
// ...
您可以使用 XmlReader 处理最大为 2 GB 的文件。
参考:How to read XML from a file by using Visual C#
【讨论】:
IIRC,.NET 2.0 以上,MS 建议直接使用 XmlReader 类而不是 XmlTextReader。 @Cerebrus 和 Mitch:谢谢先生们。这几乎就是我的想法,但是在浪费几天时间寻找可能的错误路径之前获得第二(知情)意见真的非常好。非常感谢! 在 CF 中也使用特殊的 ctor。 XmlReader.Create 进行了一些优化,并且记得使用 .Skip() 跳过无趣的元素! “最大为 2 GB 的文件” - 我找不到解释此限制的参考资料,而且似乎没有其他人提及它。你有解释这个限制的链接吗? @Nickolay MSDN 在此处引用了 2GB 限制:msdn.microsoft.com/en-us/library/ff647804.aspx:“您只能使用 XmlTextReader 和 XmlValidatingReader 来处理最大 2 GB 大小的文件。如果需要处理较大的文件,将源文件分成多个较小的文件或流。”【参考方案2】:Asat 2009 年 5 月 14 日:我已改用混合方法...请参阅下面的代码。
此版本具有两者的大部分优点: * XmlReader/XmlTextReader(内存效率 --> 速度);和 * XmlSerializer(代码生成 --> 开发权宜性和灵活性)。
它使用 XmlTextReader 遍历文档,并创建“doclet”,使用 XmlSerializer 和 XSD.EXE 生成的“XML 绑定”类对其进行反序列化。
我猜这个秘诀是普遍适用的,而且速度很快……我在大约 7 秒内解析了一个包含 56,000 个 GML 特征的 201 MB XML 文档……这个应用程序的旧 VB6 实现需要几分钟(甚至几小时) ) 来解析大型提取...所以我看起来很高兴。
再次,BIG感谢论坛会员们贡献了宝贵的时间。我真的很感激。
大家干杯。基思。
using System;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Generic;
using nrw_rime_extract.utils;
using nrw_rime_extract.xml.generated_bindings;
namespace nrw_rime_extract.xml
internal interface ExtractXmlReader
rimeType read(string xmlFilename);
/// <summary>
/// RimeExtractXml provides bindings to the RIME Extract XML as defined by
/// $/Release 2.7/Documentation/Technical/SCHEMA and DTDs/nrw-rime-extract.xsd
/// </summary>
internal class ExtractXmlReader_XmlSerializerImpl : ExtractXmlReader
private Log log = Log.getInstance();
public rimeType read(string xmlFilename)
log.write(
string.Format(
"DEBUG: ExtractXmlReader_XmlSerializerImpl.read(0)",
xmlFilename));
using (Stream stream = new FileStream(xmlFilename, FileMode.Open))
return read(stream);
internal rimeType read(Stream xmlInputStream)
// create an instance of the XmlSerializer class,
// specifying the type of object to be deserialized.
XmlSerializer serializer = new XmlSerializer(typeof(rimeType));
serializer.UnknownNode += new XmlNodeEventHandler(handleUnknownNode);
serializer.UnknownAttribute +=
new XmlAttributeEventHandler(handleUnknownAttribute);
// use the Deserialize method to restore the object's state
// with data from the XML document.
return (rimeType)serializer.Deserialize(xmlInputStream);
protected void handleUnknownNode(object sender, XmlNodeEventArgs e)
log.write(
string.Format(
"XML_ERROR: Unknown Node at line 0 position 1 : 2\t3",
e.LineNumber, e.LinePosition, e.Name, e.Text));
protected void handleUnknownAttribute(object sender, XmlAttributeEventArgs e)
log.write(
string.Format(
"XML_ERROR: Unknown Attribute at line 0 position 1 : 2='3'",
e.LineNumber, e.LinePosition, e.Attr.Name, e.Attr.Value));
/// <summary>
/// xtractXmlReader provides bindings to the extract.xml
/// returned by the RIME server; as defined by:
/// $/Release X/Documentation/Technical/SCHEMA and
/// DTDs/nrw-rime-extract.xsd
/// </summary>
internal class ExtractXmlReader_XmlTextReaderXmlSerializerHybridImpl :
ExtractXmlReader
private Log log = Log.getInstance();
public rimeType read(string xmlFilename)
log.write(
string.Format(
"DEBUG: ExtractXmlReader_XmlTextReaderXmlSerializerHybridImpl." +
"read(0)",
xmlFilename));
using (XmlReader reader = XmlReader.Create(xmlFilename))
return read(reader);
public rimeType read(XmlReader reader)
rimeType result = new rimeType();
// a deserializer for featureClass, feature, etc, "doclets"
Dictionary<Type, XmlSerializer> serializers =
new Dictionary<Type, XmlSerializer>();
serializers.Add(typeof(featureClassType),
newSerializer(typeof(featureClassType)));
serializers.Add(typeof(featureType),
newSerializer(typeof(featureType)));
List<featureClassType> featureClasses = new List<featureClassType>();
List<featureType> features = new List<featureType>();
while (!reader.EOF)
if (reader.MoveToContent() != XmlNodeType.Element)
reader.Read(); // skip non-element-nodes and unknown-elements.
continue;
// skip junk nodes.
if (reader.Name.Equals("featureClass"))
using (
StringReader elementReader =
new StringReader(reader.ReadOuterXml()))
XmlSerializer deserializer =
serializers[typeof (featureClassType)];
featureClasses.Add(
(featureClassType)
deserializer.Deserialize(elementReader));
continue;
// ReadOuterXml advances the reader, so don't read again.
if (reader.Name.Equals("feature"))
using (
StringReader elementReader =
new StringReader(reader.ReadOuterXml()))
XmlSerializer deserializer =
serializers[typeof (featureType)];
features.Add(
(featureType)
deserializer.Deserialize(elementReader));
continue;
// ReadOuterXml advances the reader, so don't read again.
log.write(
"WARNING: unknown element '" + reader.Name +
"' was skipped during parsing.");
reader.Read(); // skip non-element-nodes and unknown-elements.
result.featureClasses = featureClasses.ToArray();
result.features = features.ToArray();
return result;
private XmlSerializer newSerializer(Type elementType)
XmlSerializer serializer = new XmlSerializer(elementType);
serializer.UnknownNode += new XmlNodeEventHandler(handleUnknownNode);
serializer.UnknownAttribute +=
new XmlAttributeEventHandler(handleUnknownAttribute);
return serializer;
protected void handleUnknownNode(object sender, XmlNodeEventArgs e)
log.write(
string.Format(
"XML_ERROR: Unknown Node at line 0 position 1 : 2\t3",
e.LineNumber, e.LinePosition, e.Name, e.Text));
protected void handleUnknownAttribute(object sender, XmlAttributeEventArgs e)
log.write(
string.Format(
"XML_ERROR: Unknown Attribute at line 0 position 1 : 2='3'",
e.LineNumber, e.LinePosition, e.Attr.Name, e.Attr.Value));
【讨论】:
【参考方案3】:只是总结一下,让任何在 google 中找到此线程的人都能更清楚地了解答案。
在 .NET 2 之前,XmlTextReader 是标准 API 中可用的内存效率最高的 XML 解析器(thanx Mitch;-)
.NET 2 引入了 XmlReader 类,它再次变得更好它是一个只进的元素迭代器(有点像 StAX 解析器)。 (thanx Cerebrus;-)
记住小子,任何 XML 实例都有可能大于 500k,不要使用 DOM!
大家干杯。基思。
【讨论】:
【参考方案4】:SAX 解析器可能是您正在寻找的。 SAX 不需要您将整个文档读入内存——它会逐步解析它并允许您随时处理元素。我不知道 .NET 中是否提供了 SAX 解析器,但您可以查看一些开源选项:
http://saxdotnet.sourceforge.net/ http://www.codeguru.com/csharp/csharp/cs_data/xml/article.php/c4221这是一个相关的帖子:
SAX vs XmlTextReader - SAX in C#【讨论】:
比较一下 Sax v XmlTextReader 的性能会很有趣 - 有没有人试过这个 我也有兴趣,没比较过 .NET 不提供本机 sax 解析器,但我阅读了一篇文章(我认为是 slashdot),它展示了使用 XmlReader“原语”滚动您自己的 SAX 解析器是多么容易"。【参考方案5】:只是想添加这个简单的扩展方法作为使用 XmlReader 的示例(正如 Mitch 回答的那样):
public static bool SkipToElement (this XmlReader xmlReader, string elementName)
if (!xmlReader.Read ())
return false;
while (!xmlReader.EOF)
if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == elementName)
return true;
xmlReader.Skip ();
return false;
及用法:
using (var xml_reader = XmlReader.Create (this.source.Url))
if (!SkipToElement (xml_reader, "Root"))
throw new InvalidOperationException ("XML element \"Root\" was not found.");
if (!SkipToElement (xml_reader, "Users"))
throw new InvalidOperationException ("XML element \"Root/Users\" was not found.");
...
【讨论】:
很好...一个建议的改进:如果所寻找的元素始终是当前操作的终端(它必须将我们的读者跳过到 EOF),那么只需在 SkipTo 中直接抛出异常而不是返回 false...您已获得要报告的元素名称,因此请使用它而不是在错误消息中重复自己。 是的,你说得对。只是在我的具体情况下,我需要告诉丢失元素的完整路径,而不仅仅是它的名称。以上是关于在 C# 代码中解析(大)XML 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章