在 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 文档中搜索值列表的主要内容,如果未能解决你的问题,请参考以下文章