没有找到合适的产品?
联系客服协助选型:023-68661681
提供3000多款全球软件/控件产品
针对软件研发的各个阶段提供专业培训与技术咨询
根据客户需求提供定制化的软件开发服务
全球知名设计软件,显著提升设计质量
打造以经营为中心,实现生产过程透明化管理
帮助企业合理产能分配,提高资源利用率
快速打造数字化生产线,实现全流程追溯
生产过程精准追溯,满足企业合规要求
以六西格玛为理论基础,实现产品质量全数字化管理
通过大屏电子看板,实现车间透明化管理
对设备进行全生命周期管理,提高设备综合利用率
实现设备数据的实时采集与监控
利用数字化技术提升油气勘探的效率和成功率
钻井计划优化、实时监控和风险评估
提供业务洞察与决策支持实现数据驱动决策
转帖|其它|编辑:郝浩|2009-03-20 09:41:23.000|阅读 446 次
概述:在思考表达式树的缓存问题时,这种字符串拼接的方式只存在于脑海当中,而上文的实现是为了这一系列文章的完整性而特地编写的。
# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>
在上一篇文章里我们设法将前缀树构造为一个唯一的字符串,然后使用字符串作为key缓存在字典中。这个想法非常直接,做法也不困难(在遍历时记录详细信息便可)。不过事实上,老赵在思考表达式树的缓存问题时,这种字符串拼接的方式只存在于脑海当中,而上文的实现是为了这一系列文章的完整性而特地编写的。这是因为它的缺点较为明显,正如上文所述,字符串拼接操作较为耗时耗资源,且很容易生成一个长度可观的字符串(并非不能优化,不过实现就复杂了)。于是我们现在设法选择另一个解决方案来处理这个问题。
不过现在,我们先来考虑另一个问题:比较两个字符串是否相同(例如实现一个String.Equals方法)。此时,我们往往会分几步进行:
以上算法基本上是我们可以写出的最高效的比较算法。为什么这么说呢?原因主要有以下两点:
那么我们来思考一下,为什么前一篇文章中谈到的字符串会性能差呢?其实他正是由于违反了上面的“特点”:
与字符串比较略有不同,既然我们最终是要匹配完全相同的表达式树,那么一次完整的树遍历是不可避免的,因此第一点可能很难有所改变。那么第二点呢?我们是否可以在进行匹配时,不用保存之前已经遍历的节点,遍历到某个“冲突”则表明匹配失败,而经过一个完整遍历,则表明匹配成功。为了进一步看清需求,我们再说的清楚一点:
“序列”、“遍历”、“匹配”……在心中反复默念几遍,您是否想到了些什么?没错,上述需求似乎符合我们常用的数据结构:前缀树。
前缀树可用于查询一个集合中是否包含某个特定序列。上图(引自Wikipedia)表明了前缀树的查询方式。例如我们要查询序列“ted”,则会从根节点开始,顺着“t”、“e”、“d”依次遍历到叶结点。如果序列没有遍历完就已经到了叶结点,则说明集合中没有该序列。前缀树至少有以下两个好处:
有了思路之后,实现并不是什么大问题。如果要简单地构造一颗前缀树,最常用的方式便是让前缀树的每个节点包含一个哈希表,而每一次查找(Lookup)其实就是一次哈希表的查询操作。以下是PrefixTreeVisitor(也是另一个ExpressionVisitor的子类)中构造的预备代码:
public class PrefixTreeVisitor : ExpressionVisitor { private Hashtable m_storage; public Hashtable CurrentStorage { get; private set; } public bool StopLookingUpWhenMissed { get; private set; } public PrefixTreeVisitor(Hashtable storage,
bool stopLookingUpWhenMissed) { this.m_storage = storage; this.StopLookingUpWhenMissed = stopLookingUpWhenMissed; } public Hashtable Accept(Expression exp) { this.CurrentStorage = this.m_storage; this.Visit(exp); return this.CurrentStorage; } }
PrefixTreeVisitor的构造函数会接受两个参数:一个作为前缀树根节点的Hashtable,以及一个标识“是否在命中失败的时候停止”。如果第二个参数为true,则对于前缀树的构造将在某次查找命中失败时中止,显然它可用于对前缀树的“查询”;如果第二个参数为false,那么在查找命失败后扩展前缀树,也就是创建新的节点(Hashtable),并建立与“父节点”的关系。这段逻辑可以从关键性的LookUp方法中得到体现:
private static readonly object s_nullKey = new object(); /// <summary> /// /// </summary> /// <param name="key"></param> /// <returns>Stop or not</returns> protected virtual bool LookUp(object key) { if (this.CurrentStorage == null) return true; key = key ?? s_nullKey; Hashtable next = this.CurrentStorage[key] as Hashtable; if (next == null && !this.StopLookingUpWhenMissed) { next = new Hashtable(); this.CurrentStorage[key] = next; } this.CurrentStorage = next; return next == null; }
LookUp方法的返回值表示该次查询是否跳出。从代码中可以看出,只有当StopLookingUpWhenMissed为true时LookUp方法才会可能返回false,因为在StopLookingUpWhenMissed为false时,LookUp方法会不断地“拓展”前缀树,永远不会中止。至于在PrefixTreeVisitor的Visit等重载方法中,便是构造合适的的节点(在这里一般使用匿名类型),作为正在遍历的序列中的某个元素,并交给LookUp方法进行“深入”。如果LookUp方法返回false,则立即返回,因为前缀树已经“探底”,可以“反弹”了:
protected override Expression Visit(Expression exp) { if (exp == null) return exp; var key = new { NodeType = exp.NodeType, Type = exp.Type }; if (this.LookUp(key)) return exp; return base.Visit(exp); } protected override Expression VisitBinary(BinaryExpression b) { var key = new { IsLifted = b.IsLifted, IsLiftedToNull = b.IsLiftedToNull, Method = b.Method }; if (this.LookUp(key)) return b; return base.VisitBinary(b); } protected override Expression VisitConstant
(ConstantExpression c) { if (this.LookUp(c.Value)) return c; return base.VisitConstant(c); } protected override Expression VisitMemberAccess
(MemberExpression m) { if (this.LookUp(m.Member)) return m; return base.VisitMemberAccess(m); } protected override Expression VisitMethodCall
(MethodCallExpression m) { if (this.LookUp(m.Method)) return m; return base.VisitMethodCall(m); } ...
再编写PrefixTreeCache之前,我们可能还需要准备它的读取与写入功能。这两个操作其实都是对PrefixTreeVisitor的简单封装:
public class PrefixTreeCache<T> : IExpressionCache<T>
where T : class { private Hashtable m_storage = new Hashtable(); private T Get(Expression key) { var visitor = new PrefixTreeVisitor
(this.m_storage, true); var storage = visitor.Accept(key); return storage == null ? null :
(T)storage[typeof(T)]; } private void Set(Expression key, T value) { var visitor = new PrefixTreeVisitor
(this.m_storage, false); var storage = visitor.Accept(key); storage[typeof(T)] = value; } }
现在编写IExpressionCache.Get方法已如探囊取物:
private ReaderWriterLockSlim m_rwLock =
new ReaderWriterLockSlim(); public T Get(Expression key, Func<Expression, T> creator) { T value; this.m_rwLock.EnterReadLock(); try { value = this.Get(key); if (value != null) return value; } finally { this.m_rwLock.ExitReadLock(); } this.m_rwLock.EnterWriteLock(); try { value = this.Get(key); if (value != null) return value; value = creator(key); this.Set(key, value); return value; } finally { this.m_rwLock.ExitWriteLock(); } }
使用前缀树缓存方式,在已有n个对象的缓存容器中查询带有m个节点的表达式树,其时间复杂度正如之前所谈到那样为O(m)——与n无关。这个解决方案的缺点在于构造了大量的Hashtable,可能会导致整个前缀树处于一种非常稀疏的状态,这点可以通过自定义查找方式来替换直接使用Hashtable类进行优化。这属于纯粹的进一步实践,感兴趣的朋友可以自己尝试一下。
完整代码下载:http://code.msdn.microsoft.com/ExpressionCache
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com
文章转载自:博客园面对“数字中国”建设和中国制造2025战略实施的机遇期,中车信息公司紧跟时代的步伐,以“集约化、专业化、标准化、精益化、一体化、平台化”为工作目标,大力推进信息服务、工业软件等核心产品及业务的发展。在慧都3D解决方案的实施下,清软英泰建成了多模型来源的综合轻量化显示平台、实现文件不失真的百倍压缩比、针对模型中的大模型文件,在展示平台上进行流畅展示,提升工作效率,优化了使用体验。
本站的模型资源均免费下载,登录后即可下载。模型仅供学习交流,勿做商业用途。
本站的模型资源均免费下载,登录后即可下载。模型仅供学习交流,勿做商业用途。
本站的模型资源均免费下载,登录后即可下载。模型仅供学习交流,勿做商业用途。
服务电话
重庆/ 023-68661681
华东/ 13452821722
华南/ 18100878085
华北/ 17347785263
客户支持
技术支持咨询服务
服务热线:400-700-1020
邮箱:sales@evget.com
关注我们
地址 : 重庆市九龙坡区火炬大道69号6幢
慧都科技 版权所有 Copyright 2003-
2025 渝ICP备12000582号-13 渝公网安备
50010702500608号