C#查询XML文档symbol属性列表值的性能优化问题咨询
优化方案整理
1 一次性替代循环查询的最优方案
你当前场景下XML总节点仅7000个,总量极小,完全不需要多次执行XPath查询,最优方式是单次遍历所有item节点,匹配传入的symbol HashSet直接筛选,仅需扫描XML一次,时间复杂度O(7000),远低于循环查询的O(100*7000),也避免了拼接长XPath的语法风险和注入问题。
2 动态查询的编译能力利用
XPathExpression本身支持参数化扩展,需要搭配自定义XsltContext实现,但对你的场景性价比极低,更推荐两种更实用的方案:
- 如果是高频查询场景:直接预编译整个XML到内存字典,不需要用到XPath编译能力,查询性能直接达到O(1)
- 如果确实需要参数化XPath查询:可以将所有查询条件拼接后单次编译执行,100个or条件的XPath长度完全在可接受范围内,仅编译一次即可,不需要循环编译。
3 性能提升的其他建议
- 优先做全量内存缓存:你的XML仅3-4M,全量加载到
Dictionary<string, Info>内存占用不足10M,程序启动时或首次调用时加载一次即可,后续查询直接走内存字典,性能提升可达百倍以上。如果XML文件会更新,搭配FileSystemWatcher监听文件变更,变更后自动刷新缓存即可。 - 若不使用缓存,优先用
XmlReader流式读取:不需要加载整个DOM到内存,边读边匹配symbol,比XPathDocument内存占用更低、速度更快。 - 避免重复操作:你现有代码每次循环都编译XPath表达式,编译开销远高于查询本身,即便保留循环查询逻辑,也建议拼接所有条件后单次编译执行。
- 优化值解析:
float.TryParse指定CultureInfo.InvariantCulture,既避免不同地区小数点格式解析错误,也能小幅提升解析性能。
优化代码示例
方案1:带缓存的最优实现(高频调用场景)
using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Xml; namespace Library { public class Info { public float Val1 { get; set; } public float Val2 { get; set; } public float Val3 { get; set; } } public class Technical { // 全局缓存 private static Dictionary<string, Info> _cache; private static string _xmlPath; private static FileSystemWatcher _watcher; private static object _lockObj = new object(); public Technical() { _xmlPath = Path.Combine(CommonObjects.Constant.SettingsFolderPath, CommonObjects.Constant.TechnicalFileName); // 初始化缓存和文件监听 if (_cache == null) { RefreshCache(); InitWatcher(); } } private void RefreshCache() { lock (_lockObj) { var newCache = new Dictionary<string, Info>(); using (var reader = XmlReader.Create(_xmlPath)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "item") { string symbol = reader.GetAttribute("symbol"); if (string.IsNullOrEmpty(symbol)) continue; var info = new Info(); if (float.TryParse(reader.GetAttribute("val1"), NumberStyles.Float, CultureInfo.InvariantCulture, out float val1)) info.Val1 = val1; if (float.TryParse(reader.GetAttribute("val2"), NumberStyles.Float, CultureInfo.InvariantCulture, out float val2)) info.Val2 = val2; if (float.TryParse(reader.GetAttribute("val3"), NumberStyles.Float, CultureInfo.InvariantCulture, out float val3)) info.Val3 = val3; newCache[symbol] = info; } } } _cache = newCache; } } private void InitWatcher() { _watcher = new FileSystemWatcher(Path.GetDirectoryName(_xmlPath), Path.GetFileName(_xmlPath)); _watcher.Changed += (s, e) => RefreshCache(); _watcher.EnableRaisingEvents = true; } public Dictionary<string, Info> SearchForSymbols(HashSet<string> symbolList) { var result = new Dictionary<string, Info>(); if (symbolList.Count == 0 || _cache.Count == 0) return result; foreach (var symbol in symbolList) { if (_cache.TryGetValue(symbol, out Info info)) { result[symbol] = info; } } return result; } } }
方案2:无缓存单次查询实现(低频调用场景)
public Dictionary<string, Info> SearchForSymbols(HashSet<string> symbolList) { var result = new Dictionary<string, Info>(); if (symbolList.Count == 0) return result; string xmlPath = Path.Combine(CommonObjects.Constant.SettingsFolderPath, CommonObjects.Constant.TechnicalFileName); using (var reader = XmlReader.Create(xmlPath)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "item") { string symbol = reader.GetAttribute("symbol"); if (string.IsNullOrEmpty(symbol) || !symbolList.Contains(symbol)) continue; var info = new Info(); if (float.TryParse(reader.GetAttribute("val1"), NumberStyles.Float, CultureInfo.InvariantCulture, out float val1)) info.Val1 = val1; if (float.TryParse(reader.GetAttribute("val2"), NumberStyles.Float, CultureInfo.InvariantCulture, out float val2)) info.Val2 = val2; if (float.TryParse(reader.GetAttribute("val3"), NumberStyles.Float, CultureInfo.InvariantCulture, out float val3)) info.Val3 = val3; result[symbol] = info; // 所有symbol都找到可提前退出 if (result.Count == symbolList.Count) break; } } } return result; }
内容的提问来源于stack exchange,提问作者WiredCordless
相关产品推荐
相关产品推荐

