在C#中解析XML文件的最快方法?

SMU*_*hah 4 c# xml sql c#-4.0

我必须从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>
Run Code Online (Sandbox Code Playgroud)

我在用

XmlNodeList list = _document.SelectNodes("/player-profile/batting-statistics/odi-stats");
Run Code Online (Sandbox Code Playgroud)

然后使用foreachas 循环此列表

foreach (XmlNode stats in list)
  {
     _btMatchType = GetInnerString(stats, "matchtype"); //it returns null string if node not availible
     .
     .
     .
     .
     _btAvg = Convert.ToDouble(stats["average"].InnerText);
  }
Run Code Online (Sandbox Code Playgroud)

即使我在离线加载所有文件,解析也很慢有没有更好的解析方法?或者它是SQL的问题?我正在使用带有插入命令的DataSet,TableAdapters将所有提取的数据从XML保存到数据库.

编辑:现在使用XmlReader请为上面的文档提供一些XmlReader代码.现在,我做到了这一点

void Load(string url) 
{
    _reader = XmlReader.Create(url); 
    while (_reader.Read()) 
    { 
    } 
} 
Run Code Online (Sandbox Code Playgroud)

XmlReader的可用方法令人困惑.我需要的是完全击球和保龄球数据,击球和保龄球统计数据是不同的,而odi,t2o,ipl等在保龄球和击球中是相同的.

Car*_*rra 9

您可以使用XmlReader仅用于快进,快速阅读.

  • 嗯; 如果有很多缺失元素,那么性能问题的一部分可能与抛出的异常数量有关.例外是昂贵的.在引用之前检查节点的存在要便宜得多. (2认同)

Rob*_*ney 7

抛出异常的开销可能使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;
   }
}
Run Code Online (Sandbox Code Playgroud)

此代码将处理您关注其名称的所有元素,并忽略您不关注的元素.如果map中的值为null,则表示具有该名称的元素不存在(或没有文本).

实际上,如果您将数据放入a中DataTable,并且其中的列名DataTable与XML中的元素名称相同,则您甚至不需要构建映射,因为该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() + ".");
   }
}
Run Code Online (Sandbox Code Playgroud)

请注意我没有声明任何变量来存储这些信息,所以我没有机会搞砸并声明一个与它存储的列不同的数据类型的变量,或者在我的表中创建一个列并忘记实现填充它的逻辑.

编辑

好的,这里的东西有点棘手.这是Python中非常常见的技术; 在C#中我认为大多数人仍然认为它有些奇怪.

如果你看一下我给出的第二个例子,你可以看到它正在使用元信息DataColumn来确定用于将元素的值从文本转换为基类型的逻辑.您可以通过构建自己的地图来完成同样的事情,例如:

Dictionary<string, Type> typeMap = new Dictionary<string, Type>
{
   { "matchtype", typeof(string) },
   { "matches", typeof(int) },
   { "ballsbowled", typeof(int) }
}
Run Code Online (Sandbox Code Playgroud)

然后做我在第二个例子中展示的相同的东西:

if (typeMap[elm.Name] == typeof(int))
{
   result[elm.Name] = Convert.ToInt32(elm.Text);
   continue;
}
Run Code Online (Sandbox Code Playgroud)

你的结果不再是a 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) }
};
Run Code Online (Sandbox Code Playgroud)

如果你不习惯lambda表达式,这有点难以阅读.该类型Func<string, object>指定一个函数,该函数将a string作为其参数并返回一个对象.这就是该映射中的值是什么:它们是lambda表达式,也就是说函数.它们接受一个字符串参数(x),然后返回一个对象.(我们怎么知道这x是一个字符串?Func<string, object>告诉我们.)

这意味着转换元素可以占用一行代码:

result[elm.Name] = conversionMap[typeMap[elm.Name]](elm.Text);
Run Code Online (Sandbox Code Playgroud)

从内部表达式转到外部表达式:这会查找元素的类型typeMap,然后查找转换函数conversionMap,并调用该函数,将其elm.Text作为参数传递.

在您的情况下,这可能不是理想的方法.我真的不知道.我在这里展示它是因为有一个更大的问题在起作用.正如Steve McConnell在Code Complete中指出的那样,调试数据比调试代码更容易.此技术允许您将程序逻辑转换为数据.在某些情况下,使用此技术可以大大简化程序的结构.值得了解.