【问题标题】:Searching XML document for a list of values在 XML 文档中搜索值列表
【发布时间】:2021-12-09 13:31:03
【问题描述】:

我有一个包含大约 7000 个节点的 XML 文件(每行一个节点,没有级联节点),每个节点有大约 15 到 20 个保存十进制值的属性。 xml 文件大小约为 3 到 4 Mb。在每个节点中,符号属性都有唯一的值。

目标是通过匹配“符号”属性来搜索节点。

我有以下列出的方法,它将符号列表作为输入 (symbolList)。为了执行搜索,XPathDocument 从硬盘加载 XML 文件,对循环中的每个符号执行搜索,并以字典的形式返回结果。这些符号(输入)可以是 10 或 100 等(不固定)

为了执行搜索,我为每个符号运行了一个 for-each 循环。

问题:

(1) 搜索所有符号的另一种有效方法是什么? 一枪并删除一次搜索一个符号的循环。

在下面的代码中,我对效率不满意。 XPathNavigator 在循环中一次执行一个符号搜索,它检索匹配节点,读取属性值,并在集合中添加值。我想删除循环,它一次搜索一个符号。

我曾考虑通过添加所有带有“或”条件的符号来构建一个 XPath 查询,但是当我有 100 个左右的符号要搜索时,它可能是一个很大的 XPath 查询。有没有更好的解决方案来减少扫描次数?

(2) 如何利用 XPath 查询“编译”这个动态搜索?

我可以编译 XPath 查询来构建 XPathExpression,但这只有在我的 XPath 对多次扫描保持相同时才有用,而且我没有找到编译查询的方法,我可以将搜索 @parameter 值提供给编译的查询。有没有办法或任何例子来使用带参数的 Xslt 模板(作为字符串)?

(3) 还有什么其他建议可以减少 CPU 周期并使此代码比当前代码运行得更快吗?我并不是说这段代码很慢,但我想让它尽可能快。

Xml 文档示例:

<?xml version="1.0" encoding="utf-8"?>
<items>
  <item symbol="ABC" val1="46.21717" val2="152.39" val3="158.121" />
  <item symbol="CJKM" val1="51.21659" val2="49.8" val3="57.57" />
  <item symbol="FWML" val1="67.99509" val2="9.75" val3="9.84" />
  <item symbol="JSHR" val1="48.67459" val2="2.27" val3="2.9" />
  <item symbol="DIBG" val1="53.60444" val2="26.04" val3="28" />
  <item symbol="GHLH" val1="42.31754" val2="0.1016" val3="0.1192" />
  <item symbol="ICWE" val1="58.39788" val2="3.855" val3="3.99" />
  <item symbol="LPVN" val1="47.03581" val2="19.22" val3="20.15" />
  <item symbol="MCAT" val1="57.83422" val2="23.0969" val3="26.59" />
  <item symbol="ZYXI" val1="54.94584" val2="11.6784" val3="12.9" />
</items>

C# 代码:

using System.Collections.Generic;
using System.IO;
using System.Xml.XPath;

namespace Library
{
    public class Info
    {         
        public float Val1 { get; set; }
        public float Val2 { get; set; }
        public float Val2 { get; set; }
     }

    public class Technical
    {    
        public Dictionary<string, Info> SearchForSymbols(HashSet<string> symbolList)
        {
            Dictionary<string, Info> dictSearchResult = new Dictionary<string, Info>();

            if (symbolList.Count == 0)
            {
                return dictSearchResult;
            }

            FileInfo fileInfo = new FileInfo(Path.Combine(CommonObjects.Constant.SettingsFolderPath, CommonObjects.Constant.TechnicalFileName));

            XPathDocument document = new XPathDocument(fileInfo.FullName);
            XPathNavigator navigator = document.CreateNavigator();

            string symbol;
            float val1;
            float val2;
            float val3;

            XPathNavigator node;
            Info info;

            foreach (string item in symbolList)
            {
                XPathExpression expression = navigator.Compile($"//items/item[@symbol='{item}']");

                node = navigator.SelectSingleNode(expression);

                if (node == null)
                {
                    continue;
                }

                symbol = node.GetAttribute("symbol", "");

                if (string.IsNullOrEmpty(symbol))
                {
                    continue;
                }

                info = new Info();

                // Get Value 1
                if (float.TryParse(node.GetAttribute("val1", ""), out val1))
                {
                    info.Val1 = val1;
                }

                // Get Value 2
                if (float.TryParse(node.GetAttribute("val2", ""), out val2))
                {
                    info.Val2 = val2;
                }

                // Get Value 3
                if (float.TryParse(node.GetAttribute("val3", ""), out val3))
                {
                    info.Val3 = val3;
                }

                if (!dictSearchResult.ContainsKey(symbol))
                {
                    dictSearchResult.Add(symbol, info);
                }
            }

            return dictSearchResult;
        }
    }
}

【问题讨论】:

  • 你有没有先做任何性能指标?比较加载字典和比较使用 XPath 的时间。

标签: c# xml xpath xpathnavigator


【解决方案1】:

这基本上是一个连接查询,而你的算法是一个嵌套循环连接,这绝对是不理想的。您在循环中重新编译 XPath 表达式这一事实加剧了问题。

要搜索 100 个符号,最好将符号放入某种查找结构(例如 HashSet)中,然后对输入进行一次扫描,测试每个项目以查看其键是否存在于哈希集。

理想情况下,您不希望将逻辑拆分为两种不同的语言(C# 和 XPath)。我不知道 Microsoft 技术的调用开销有多高,但它肯定是可观的。

使用 XPath 3.1(在 SaxonCS 中可用),您可以在一个 XPath 表达式中完成所有这些操作:

let $symbols := map{"AAA":1, "BBB":1, "CCC":1}
return //items/item[map:contains($symbols, @symbol)] 
   ! map{ "symbol": string(@symbol),
          "val1": number(@val1),
          "val2": number(@val2),
          "val3": number(@val2) }

然后有一个从 C# 到 XPath 的调用,在 API 层进行一些操作以提供符号的输入列表并提取输出。

但是,如果您想坚持当前的技术,通过对数据进行单次扫描并根据所需符号的字典测试每个项目,仍有相当大的改进可能。

【讨论】:

    【解决方案2】:

    对于您的问题,这可能是完全不同的实现,但您是否尝试将 XML 文档加载到 DataSet。像这样:

     Dictionary<string, Info> dictSearchResult = new Dictionary<string, Info>();
     DataSet ds = new DataSet();
     ds.ReadXml(filePath);
     DataTable dt = ds.Tables[0];
    

    Your DataTable will look like this

    然后使用 LINQ 搜索您的符号,如下所示:

     IEnumerable<DataRow> searchedSymbolsRows = from r in dt.AsEnumerable()
                           join s in symbolList 
                           on r.Field<string>("symbol") equals s
                           select r;
    

    然后你可以用你的DataRow集合做任何你想做的事情

        foreach (DataRow item in searchedSymbolsRows)
        {
            string symbol = item[0].ToString();
            float val1;
            float val2;
            float val3;
            var symbolInfo = new Info
            {
                Val1 = float.TryParse(item[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out val1) ? val1 : 0,
                Val2 = float.TryParse(item[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out val2) ? val2 : 0,
                Val3 = float.TryParse(item[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out val3) ? val3 : 0,
            };
    
            dictSearchResult.Add(symbol,symbolInfo);
    
            Console.WriteLine($"Symbol {symbol} added to dictionary");
    
        }
    

    注意:我不知道这种方法是否会比您的实际实现有更好的性能,但它可以是另一种选择。

    【讨论】:

      【解决方案3】:

      我的偏好是将 XML Linq 与字典一起使用:

      using System;
      using System.Linq;
      using System.Xml;
      using System.Xml.Linq;
      using System.Text;
      using System.Collections;
      using System.Collections.Generic;
      
      namespace ConsoleApp2
      {
          class Program
          {
              const string FILENAME = @"c:\temp\test.xml";
              static void Main(string[] args)
              {
                  XDocument doc = XDocument.Load(FILENAME);
                  Dictionary<string, Info> dict = doc.Descendants("item")
                      .Select(x => new Info() { symbol = (string)x.Attribute("symbol"), Val1 = (float)x.Attribute("val1"), Val2 = (float)x.Attribute("val2"), Val3 = (float)x.Attribute("val3") })
                      .GroupBy(x => x.symbol, y => y)
                      .ToDictionary(x => x.Key, y => y.FirstOrDefault());
              }
       
          }
              public class Info
              {
                  public string symbol { get; set; }
                  public float Val1 { get; set; }
                  public float Val2 { get; set; }
                  public float Val3 { get; set; }
              }
      
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多