在 XML 文档中搜索值列表

Posted

技术标签:

【中文标题】在 XML 文档中搜索值列表【英文标题】:Searching XML document for a list of values 【发布时间】: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 的时间。 【参考方案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; 
        

    

【讨论】:

以上是关于在 XML 文档中搜索值列表的主要内容,如果未能解决你的问题,请参考以下文章

如何搜索 xml 节点值,然后在 c# 中为该元素创建新属性

VBA搜索值并从列表中删除(for循环太慢)

搜索引擎基础概念—— 倒排列表

如何从 XML DB 中获取文档的名称列表

根据列表中的值更改 xml 字段值

You Tube搜索列表Api搜索标题或描述或标记字段?