在 C# 中解析 XML 文件的最快方法?
Posted
技术标签:
【中文标题】在 C# 中解析 XML 文件的最快方法?【英文标题】:Fastest way to parse XML files in C#? 【发布时间】:2011-03-04 01:19:43 【问题描述】:我必须从 Internet 加载许多 XML 文件。但为了以更快的速度进行测试,我下载了以下格式的所有文件(超过 500 个文件)。
<player-profile>
<personal-information>
<id>36</id>
<fullname>Adam Gilchrist</fullname>
<majorteam>Australia</majorteam>
<nickname>Gilchrist</nickname>
<shortName>A Gilchrist</shortName>
<dateofbirth>Nov 14, 1971</dateofbirth>
<battingstyle>Left-hand bat</battingstyle>
<bowlingstyle>Right-arm offbreak</bowlingstyle>
<role>Wicket-Keeper</role>
<teams-played-for>Western Australia, New South Wales, ICC World XI, Deccan Chargers, Australia</teams-played-for>
<iplteam>Deccan Chargers</iplteam>
</personal-information>
<batting-statistics>
<odi-stats>
<matchtype>ODI</matchtype>
<matches>287</matches>
<innings>279</innings>
<notouts>11</notouts>
<runsscored>9619</runsscored>
<highestscore>172</highestscore>
<ballstaken>9922</ballstaken>
<sixes>149</sixes>
<fours>1000+</fours>
<ducks>0</ducks>
<fifties>55</fifties>
<catches>417</catches>
<stumpings>55</stumpings>
<hundreds>16</hundreds>
<strikerate>96.95</strikerate>
<average>35.89</average>
</odi-stats>
<test-stats>
.
.
.
</test-stats>
<t20-stats>
.
.
.
</t20-stats>
<ipl-stats>
.
.
.
</ipl-stats>
</batting-statistics>
<bowling-statistics>
<odi-stats>
<matchtype>ODI</matchtype>
<matches>378</matches>
<ballsbowled>58</ballsbowled>
<runsgiven>64</runsgiven>
<wickets>3</wickets>
<fourwicket>0</fourwicket>
<fivewicket>0</fivewicket>
<strikerate>19.33</strikerate>
<economyrate>6.62</economyrate>
<average>21.33</average>
</odi-stats>
<test-stats>
.
.
.
</test-stats>
<t20-stats>
.
.
.
</t20-stats>
<ipl-stats>
.
.
.
</ipl-stats>
</bowling-statistics>
</player-profile>
我正在使用
XmlNodeList list = _document.SelectNodes("/player-profile/batting-statistics/odi-stats");
然后用foreach
作为循环这个列表
foreach (XmlNode stats in list)
_btMatchType = GetInnerString(stats, "matchtype"); //it returns null string if node not availible
.
.
.
.
_btAvg = Convert.ToDouble(stats["average"].InnerText);
即使我离线加载所有文件,解析也很慢 有没有更好的更快的方法来解析它们?还是SQL有问题?我正在使用 DataSets、TableAdapters 和 insert 命令将所有提取的数据从 XML 保存到数据库。
编辑: 现在要使用 XmlReader,请为上述文档提供一些 XmlReader 代码。现在,我已经这样做了
void Load(string url)
_reader = XmlReader.Create(url);
while (_reader.Read())
XmlReader 的可用方法令人困惑。我需要的是完整地获得击球和保龄球的统计数据,击球和保龄球的统计数据是不同的,而保龄球和击球的 odi、t2o、ipl 等是相同的。
【问题讨论】:
您是否尝试过使用 LINQ to XML 进行解析? 不,我没有,这是最好的方法吗? 您可能需要分离关注点(文件加载、节点提取、数据库交互等)来确定瓶颈。有几种方法可以提高每个组件的性能。 我是学生,这项工作是我项目的一部分。现在,我已经创建了一个类来解析所有这些 XML 文件并将它们从该类中保存到数据库中。是的,如果我可以拆分组件会更好,但我不知道如何,你能给我一个链接来学习吗? 【参考方案1】:您只能使用XmlReader 进行转发、快速阅读。
【讨论】:
好的!我要试试!当文档中缺少某些节点时如何处理异常,我添加了很多 try 和 catch 语句以避免像stats["average"].InnerText
这样的代码出现空引用异常,其中“平均”是节点名称
嗯;如果有很多缺失的元素,部分性能问题可能与抛出的异常数量有关。例外是昂贵的。在引用之前检查节点是否存在要便宜得多。
有一个类似 getAttribute(string statname) 的函数,它在 try/catch 块中使用 stats[statname] 并在捕获到异常时返回 string.Empty。
如果你在这里得到一个空引用,那是因为 stats["average"] 是空的。只需添加一个 (if stats["average"] != null)) 检查。
@apoorv020 我已经这样做了,GetInnerString() 完成了这项任务。【参考方案2】:
抛出异常的开销可能会使 XML 解析的开销相形见绌。您需要重写您的代码,使其不会引发异常。
一种方法是在询问元素值之前检查元素是否存在。这会起作用,但它有很多代码。另一种方法是使用地图:
Dictionary<string, string> map = new Dictionary<string, string>
"matchtype", null ,
"matches", null ,
"ballsbowled", null
;
foreach (XmlElement elm in stats.SelectNodes("*"))
if (map.ContainsKey(elm.Name))
map[elm.Name] = elm.InnerText;
此代码将处理您关心的所有元素,并忽略您不关心的元素。如果地图中的值为 null,则表示该名称的元素不存在(或没有文本)。
其实,如果你把数据放到一个DataTable
中,而DataTable
中的列名和XML中的元素名是一样的,你甚至不需要建一个map,因为 DataTable.Columns
属性是您需要的所有地图。此外,由于DataColumn
知道它包含什么数据类型,因此您不必在代码中复制该知识:
foreach (XmlElement elm in stats.SelectNodes("*"))
if (myTable.Columns.Contains(elm.Name))
DataColumn c = myTable.Columns[elm.Name];
if (c.DataType == typeof(string))
myRow[elm.Name] = elm.InnerText;
continue;
if (c.DataType == typeof(double))
myRow[elm.Name] = Convert.ToDouble(elm.InnerText);
continue;
throw new InvalidOperationException("I didn't implement conversion logic for " + c.DataType.ToString() + ".");
注意我没有声明任何变量来存储这些信息,所以我不可能搞砸并声明一个数据类型不同于它存储的列的变量,或者在我的表中创建一个列和忘记实现填充它的逻辑。
编辑
好的,这有点棘手。这是 Python 中非常常见的技术。在 C# 中,我认为大多数人仍然认为它有些奇怪。
如果您查看我给出的第二个示例,您可以看到它使用DataColumn
中的元信息来确定使用什么逻辑将元素的值从文本转换为其基本类型。您可以通过构建自己的地图来完成同样的事情,例如:
Dictionary<string, Type> typeMap = new Dictionary<string, Type>
"matchtype", typeof(string) ,
"matches", typeof(int) ,
"ballsbowled", typeof(int)
然后做我在第二个例子中展示的几乎相同的事情:
if (typeMap[elm.Name] == typeof(int))
result[elm.Name] = Convert.ToInt32(elm.Text);
continue;
您的结果不能再是Dictionary<string, string>
,因为现在它们可以包含非字符串的内容;他们必须是Dictionary<string, object>
。
但这种逻辑似乎有点笨拙;您对每个项目进行了多次测试,有continue
语句可以打破它 - 这并不可怕,但它可能更简洁。如何?通过使用另一个映射,将类型映射到转换函数:
Dictionary<Type, Func<string, object>> conversionMap =
new Dictionary<Type, Func<string, object>>
typeof(string), (x => x) ,
typeof(int), (x => Convert.ToInt32(x)) ,
typeof(double), (x => Convert.ToDouble(x)) ,
typeof(DateTime), (x => Convert.ToDateTime(x)
;
如果您不习惯 lambda 表达式,这有点难以阅读。 Func<string, object>
类型指定了一个函数,该函数将 string
作为其参数并返回一个对象。这就是该映射中的值:它们是 lambda 表达式,也就是函数。它们接受一个字符串参数 (x
),然后返回一个对象。 (我们怎么知道x
是一个字符串?Func<string, object>
告诉我们。)
这意味着转换一个元素只需一行代码:
result[elm.Name] = conversionMap[typeMap[elm.Name]](elm.Text);
从内部表达式到外部表达式:这在typeMap
中查找元素的类型,然后在conversionMap
中查找转换函数,并调用该函数,将elm.Text
作为参数传递给它。
在您的情况下,这可能不是理想的方法。我真的不知道。我在这里展示它是因为有一个更大的问题在起作用。正如 Steve McConnell 在代码完成中指出的那样,调试数据比调试代码更容易。该技术使您可以将程序逻辑转换为数据。在某些情况下,使用这种技术可以极大地简化程序的结构。值得理解。
【讨论】:
谢谢!我删除了所有 try 和 catch 语句,并将它们替换为一个函数,如果元素为 null 或不存在,则返回 null 或零。目前我将所有数据保存到预定义的变量中,然后使用 insert(var1,var2,var3 ....)。您说的方法看起来更方便,尝试学习和理解如何实现它。 哇,太好了,现在我知道第一种方法了。它要简单得多,但我只能使用第一种方法,因为我使用 tableAdapter 来存储数据。 由于 TableAdapter 的全部意义在于简化 DataTables 对 SQL 的适应,所以我觉得这条评论没有意义。 我目前使用的是Dictionary<string, string>
,但它只能存储一种类型的数据。虽然 XML 有不同类型的数据,如 int、double、DataTime、TimeSpan 等。我该如何使用字典呢?
简短的回答是您应该使用Dictionary<string, object>
。对于长答案,请参阅我的编辑。【参考方案3】:
你可以试试LINQ to XML。或者您可以使用this 来确定使用什么。
【讨论】:
谢谢,这意味着我必须使用 XmlReader,仍然在寻找一个很好的 GUI 应用教程【参考方案4】:如果您知道 XML 是一致且格式良好的,您可以简单地避免进行真正的 XML 解析,而只是将它们作为纯文本文件处理。这是有风险的、不可移植的和脆弱的。
但这将是最快的(运行,而不是编码)解决方案。
【讨论】:
-1 就如何创建有风险、不可移植和脆弱的解决方案提供建议。【参考方案5】:如果文档很大,那么基于流的解析器(可以满足您的需要)将比使用 XmlDocument 更快,主要是因为开销较低。查看 XmlReader 的文档。
【讨论】:
能否请您为我的文档提供一小段代码?【参考方案6】:我不会说 LINQ 是最好的方法。我搜索了谷歌,我看到了一些对 html Agility Pack 的引用。
我认为,如果您遇到速度瓶颈,那就是您的下载过程。换句话说,您的性能问题似乎与您的 XML 代码无关。我认为有一些方法可以提高您的下载速度或文件 i/o,但我不知道它们会是什么。
【讨论】:
不,我已经说过为了速度我已经将所有文件下载到 PC,现在我没有从互联网上获取它们。 HTML 敏捷包用于解析 html。它比解析 xml 更宽容。不过,检查下载文件是否存在瓶颈是个好主意。【参考方案7】:XmlReader 是您的问题的解决方案。 XmlDocument 存储了许多元信息,使 Xml 易于访问,但它在内存上变得过于繁重。我已经看到一些大小小于 50 KB 的 Xml 被转换为几 MB(10 左右)的 XmlDocument。
【讨论】:
你能给我的文档提供一些 XmlReader 的代码吗?到目前为止,我已经完成了这个void Load(string url) _reader = XmlReader.Create(url); while (_reader.Read())
XmlReader 的可用方法令人困惑。我需要的是完整地获得击球和保龄球的统计数据,击球和保龄球的统计数据是不同的,而保龄球和击球的 odi、t2o、ipl 等是相同的。
void Load(string url) _reader = XmlReader.Create(url); while (_reader.Read()) _reader.Name; // 给出名称 _reader.Value; // 将值作为字符串 请查看 MSDN 了解更多详细信息。您必须检查 HasValues、HasAttributes 等。
XmlReader 很难实现,XmlDocument 很简单,问题不在 XmlDocument 上,因为 try catch 语句导致速度很慢。感谢您的帮助。【参考方案8】:
如果您已经将该信息转换为 DataSet 以将其插入表中,只需使用 DataSet.ReadXML() - 并使用它根据数据创建的默认表。
这个玩具应用可以做到这一点,并且可以使用您在上面定义的格式。
项目文件:http://www.dot-dash-dot.com/files/wtfxml.zip 安装人员:http://www.dot-dash-dot.com/files/WTFXMLSetup_1_8_0.msi
它允许您使用树和网格格式浏览编辑 XML 文件 - 网格中列出的表是 DataSet 在 ReadXML() 之后自动创建的表。
【讨论】:
谢谢!我为数据库而不是 XML 制作了 DataSet。我正在解析 Xml 文件,提取数据,将此数据传递给 TableAdapter.Insert 从而将其保存到数据库,然后通过将 Gui 组件与数据库绑定来显示。以上是关于在 C# 中解析 XML 文件的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章