层级结构缓存IHiberarchyCache -- ESBasic 可复用的.NET类库(24)

1.缘起:

    从IMultiTreeIAgileMultiTree,一切进展得都不错。但是,还有改进的地方。多叉树的一个优点在于,根据指定的节点能够非常迅速地找到其所有的子节点。但是缺点在于,根据节点值的ID定位到目标节点不够快,因为需要对所有的节点进行遍历操作。当节点非常多、层次非常深时,这种定位操作可能会严重的影响效率。

    我设计了层级结构缓存ESBasic.ObjectManagement.Cache.IHiberarchyCache来加速这种根据节点值ID定位节点的访问。所谓“层级结构”,就是类似我们在IMultiTree章节缘起中介绍的那个多叉树式的组织结构。

使用IHiberarchyCache可以使两种操作都足够快:一是根据ID找到目标对象,另一种是根据ID找到其所有下级对象。

      IHiberarchyCache融合了IAgileMultiTree和Dictionary两种对象容器的优势,以达到对层级结构的高效缓存。

  层级结构缓存的形象示意图如下:

  

  从示意图可以看到,IHiberarchyCache内部是借助IAgileMultiTree和ISmartDictionaryCache来实现的。

 

2.适用场合:

   如果你在使用IAgileMultiTree的同时,经常需要根据节点值ID来定位节点,而且希望这种定位非常迅速,那么你可以改用IHiberarchyCache。

 

3.设计思想与实现

   所有希望能够存储在IHiberarchyCache中的节点值必须实现IHiberarchyVal接口,该接口从IMTreeVal继承,其定义如下:

   public interface IHiberarchyVal : IMTreeVal
    {
        string SequenceCode { get; }
    }

 

其相比于IMTreeVal增加了一个SequenceCode属性,以表明节点值在多叉树中的具体位置。

   最初的IMultiTree对节点值是否有SequenceCode并没有任何要求,也就是说没有SequenceCode特性也可以正常使用IMultiTree。接着,IAgileMultiTree将SequenceCode作为一个设计参数纳入到核心机制中。到现在IHiberarchyCache,SequenceCode已经是节点值必须实现的一个属性了,否则,IHiberarchyCache将无法正常工作。这是一个逐渐强化SequenceCode作用的过程。

   接下来我们来看IHiberarchyCache接口的定义:

    public interface IHiberarchyCache<TVal> where TVal : IHiberarchyVal
    {        
        /// <summary>
        /// RootID 设置根节点的ID。
        /// </summary>
        string RootID { set; }

        /// <summary>
        /// SequenceCodeSplitter 节点路径(序列号)的分割符。
        /// </summary>
        char SequenceCodeSplitter { get; set; }

        IObjectRetriever<string ,TVal> ObjectRetriever { set; }
        
        int Count { get; }

        void Initialize();
        
        /// <summary>
        /// Get 如果目标对象在缓存中不存在,则通过ObjectRetriever去提取。
        /// </summary>           
        TVal Get(string id);

        /// <summary>
        /// HaveContained 缓存中是否一经包含了目标对象。
        /// </summary>       
        bool HaveContained(string id);
        
        /// <summary>
        /// GetAllKeyListCopy 获取所有ID的列表的拷贝。
        /// </summary>         
        IList<string> GetAllKeyListCopy();

        /// <summary>
        /// GetAllValListCopy 获取所有的节点值列表的拷贝。
        /// </summary>       
        IList<TVal> GetAllValListCopy();
        
        /// <summary>
        /// GetChildrenOf 获取parentID的所有孩子节点的节点值列表。
        /// </summary>     
        IList<TVal> GetChildrenOf(string parentID);

        /// <summary>
        /// GetChildrenCount 获取parentID直接下级的个数。
        /// </summary>        
        int GetChildrenCount(string parentID);

        /// <summary>
        /// CreateHiberarchyTree 返回表示层级信息的最单纯的数据结构。
        /// 注意:返回的Tree实际上与内部的AgileMultiTree是引用的根节点是同一个节点。
        /// </summary>        
        MultiTree<TVal> CreateHiberarchyTree();

        /// <summary>
        /// GetNodesOnDepthIndex 获取某一深度的所有节点。Root的深度索引为0
        /// </summary> 
        IList<TVal> GetNodesOnDepthIndex(int depthIndex);

        /// <summary>
        /// GetNodesOnDepthIndex 获取所属parentID体系下并且深度为depthIndex的所有节点。Root的深度索引为0
        /// </summary>        
        IList<TVal> GetNodesOnDepthIndex(string parentID, int depthIndex);        
    }

 

RootID属性表明了根节点值的ID,IHiberarchyCache将会以该ID的节点值来初始化层级结构的根。

   接下来的SequenceCodeSplitter、ObjectRetriever和Count属性的含义我们在前面介绍IMultiTree时已经详细介绍过了,这里就不再赘述。

   以-Copy结尾的方法返回的都是对象集合的一个拷贝,在方法返回后,这个集合中的元素个数可以被修改,不会影响到IHiberarchyCache的内部缓存。

   注意,GetChildrenOf方法返回的是TVal的IList,而不是MNode的IList。这是因为在IHiberarchyCache中,节点的概念已经被淡化了,它只是在IHiberarchyCache的内部实现时使用,用于加快类似获取某个元素的所有下级对象的访问。而外部根本不用在乎IHiberarchyCache内部的具体实现方式,所以不需要将MNode暴露出来

   而且,如果IHiberarchyCache的某个方法暴露出了MNode节点对象,则有可能产生危险,因为当用户获取了某MNode节点对象的引用后,就可以调用其AddChild方法手动向IHiberarchyCache内部多叉树中添加节点,而这个节点在内部的ISmartDictionaryCache缓存中并不存在,从而导致IHiberarchyCache内部的两个缓存的状态不一致。

    CreateHiberarchyTree方法用于创建一个新的MultiTree,返回的MultiTree与IHiberarchyCache内部的多叉树拥有完全一样的结构。

    GetNodesOnDepthIndex方法的含义与IMultiTree是完全一致的。

 

   接下来我们将注意力转移到HiberarchyCache的具体实现上来。

正如示意图所展示的,HiberarchyCache内部使用了IAgileMultiTree和ISmartDictionaryCache,从而达到了我们在缘起部分希望达到的效果。但是图中还有一个细节我们应该注意到,那就是数据流的方向。我们看到,IAgileMultiTree的数据的来源是ISmartDictionaryCache,这意味着,如果目标对象在当前IHiberarchyCache实例中不存在,则会先通过IObjectRetriever将其加载到ISmartDictionaryCache中,然后IAgileMultiTree再从ISmartDictionaryCache中取出加载到多叉树中。那么,IAgileMultiTree是如何从ISmartDictionaryCache加载数据的了?是通过一个适配器,这个适配器叫做HiberarchyAgileNodePicker,其类图如下所示:

 

 

它将ISmartDictionaryCache适配为一个IAgileNodePicker对象给IAgileMultiTree使用。这一点我们可以从HiberarchyCache的Initialize方法的实现中一窥究竟。

关于HiberarchyCache的具体实现,其中的关键点罗列如下:

(1)关于线程安全的部分。由于内部仅有的两个容器ISmartDictionaryCache和IAgileMultiTree的实现都是线程安全的,所以HiberarchyCache本身也是线程安全的,而且不用做任何额外的处理。

(2)CreateHiberarchyTree方法返回的是内部多叉树的一个浅表拷贝。

(3)其它很多方法都是借助内部的IAgileMultiTree来完成的。

 

4. 使用时的注意事项

(1)     IHiberarchyCache加速了根据节点值ID定位节点的查找访问,这是通过使用额外的内存空间为代价的,即典型的“以空间换时间”的例子。幸运的是,需要重复存储的只是节点值ID,而不是整个节点值对象,所以这个额外的内存开销并不是特别大。在大多数情况下,这个开销是值得的。

(2)     在介绍IMultiTree时,我们提到不要轻易调用其Count属性,因为每次调用都会进行一次递归统计动作,可能会影响性能。但是IHiberarchyCache的Count属性却可以非常快的返回,因为它直接返回了内部字典的Count属性,而不需要做额外的动作。

(3)     IHiberarchyCache内部使用的两个缓存容器IAgileMultiTree和ISmartDictionaryCache,它们的状态是完全一致的。也就是说,如果一个对象存在于ISmartDictionaryCache中,那么在IAgileMultiTree中一定就能找到与之对应的节点,反之亦然。

(4)     CreateHiberarchyTree方法返回的多叉树是只读的,这一点要特别注意。因为它只是IHiberarchyCache内部多叉树的一个浅表拷贝,所以对它的修改会导致IHiberarchyCache的内部状态不一致。通常,我使用CreateHiberarchyTree方法都是为了将返回的实例进行持久化存储的。

 

5.扩展

    层级结构缓存IHiberarchyCache暂时没有任何扩展。

 

注: ESBasic已经开源,点击这里下载源码。  
    ESBasic开源前言

时间: 2024-11-08 23:42:41

层级结构缓存IHiberarchyCache -- ESBasic 可复用的.NET类库(24)的相关文章

Round缓存管理器RoundCacheManager--ESBasic 可复用的.NET类库(26)

1.缘起:     在增量自动获取器章节的缘起部分,我们曾提到增量缓存,本节我们将深入探讨它以及用于管理增量缓存的管理器.我们还是以增量自动获取器章节提到的例子作为基础,并做更进一步的讨论.       OK,现在让我们开始这有趣的旅程. 首先,基于前面例子给出的上下文,我们知道IIncreaseAutoRetriever获取的增量是用于累积当天的已成交订单报表的."当天已成交报表"就是一个典型的增量缓存,每当有新的增量到来,都会累加到上面. 我们假设今天是2009.07.08,那么我

智能字典缓存 ISmartDictionaryCache-- ESBasic 可复用的.NET类库(18)

1.缘起:    假设我们有一个会员管理系统,需要向各方提供查询会员基础资料的功能.会员一经注册,其基础资料就将不再发生变化(如会员帐号.身份证ID.注册时间等等).    基于这样的需求,我们可以将会员的基础资料"永久地"缓存在内存中,从而提升对任何一个会员基础资料的查询速度.    我设计了ESBasic.ObjectManagement.Cache.ISmartDictionaryCache来对这种性质的对象进行缓存.     职能字典缓存的形象示意图如下:   2.适用场合:

热缓存 IHotCache --ESBasic 可复用的.NET类库(19)

1.缘起:     假设我们有一个订单系统,现在这个系统要增加一个功能――允许客人查核他认为有问题的订单的详细信息.当客人觉得自己的某个订单不对劲时,他首先会从订单系统查询这个订单的详细信息,然后打电话告诉我们的客服有问题的订单的编号,客服再去查核,如果属实,客服还要进一步上报,如果该订单非常重要,则可能需要更进一步上报复查等.     从这个需求我们看到,同一个订单可能会在比较短的时间内查询数次甚至数十次,所以我们可以称这个订单为"热点"订单.而其它的成千上万的订单可能在一个月内都不

定时刷新缓存管理器 IRefreshableCacheManager --ESBasic 可复用的.NET类库(16)

1.缘起:     为了提升系统的性能或减轻数据库的压力等原因,我们经常在系统中使用缓存来把那些经常使用的数据保留在内存中.如果因为某些原因,缓存中这些经常使用的数据不能及时与数据源进行同步更新,那么采用定时刷新缓存中的数据有可能就是一种合适的选择.     如果你的缓存是定时刷新,那么你就需要自己为其维护一个定时器或循环引擎.如果你的系统中像这样定时刷新的缓存有多个,而且每个缓存定时刷新的时间间隔又要求不一样,那么,使这些缓存按照你预想的情况进行运转,你就需要花费一些气力.     我设计了定

多叉树 IMultiTree -- ESBasic 可复用的.NET类库(22)

1.缘起:     假设我们要描述一个集团公司的组织结构,这个集团公司的体系分为如下几层:集团.公司.子公司.部门.小组.即一个集团由多个公司构成,每个公司又有几个子公司构成,每个子公司拥有多个部门,每个部门又内分为几个小组.     很明显,这种体系结构就是一个多叉树.我设计了ESBasic.ObjectManagement.Trees.Multiple.IMultiTree来抽象多叉树,其提供了很多简便的方法让我们对多叉树进行节点查询和操作. 多叉树的形象示意图如下: 从上图中我们看到,集团

ESBasic 可复用的.NET类库(00) -- 开源前言(附下载)

自从03年正式使用.NET开发以来,已经走过了6个年头,这期间我积累了几套类库和框架,ESBasic便是其中最基础的一个类库.ESBasic是Enterprise Service Basic的缩写,虽然也简写为ESB,但是它和Enterprise Service Bus(企业服务总线)没有任何关系.ESBasic是我能够快速和高效开发应用程序的利器之一,开这个专门的blog是想将它介绍给大家,希望能对大家有所启发. ESBasic覆盖的内容包括:对象管理.插件.网络(Socket).多线程.Em

双向映射 IBidirectionalMapping -- ESBasic 可复用的.NET类库(11)

1.缘起:     假设我们的用户管理系统要求用户的ID和Name都必须是唯一的,并且用户的ID和Name一经确定就不能被修改.而且管理系统经常需要根据ID来查找Name,也经常需要根据Name来查找ID.根据这样的需求,我们可以考虑使用一个Dictionary来将ID和Name缓存起来,通常ID作为Key,Name作为Value.这样便可实现通过ID查询Name的快速查找,但是,通过Name查找ID就不是那么快了,因为涉及到对Dictionary的Values做遍历的操作.那么,有可能使得通过

对象池 IObjectPool -- ESBasic 可复用的.NET类库(15)

1.缘起:     对象池应该是一个"历史悠久"的概念了,像我们经常说的线程池.还有ADO.NET中的数据库连接池等,都属于对象池的应用.     我们的应用有时也会碰到需要使用对象池的情况,我举个例子说明一下.假设,我们需要记录某个类MyClass的每个方法每次被调用时方法执行所消耗的时间,而且,这个类是使用在多线程的环境中的,每个方法都可以同时在多个线程中执行,不需要被同步,这样可以使并发达到最大.     好,我们可以使用Stopwatch这个类来准确地记录每个方法的时间,关键是

优先级管理器 IPriorityManager -- ESBasic 可复用的.NET类库(14)

1.缘起:     假设我们的订单处理系统所要处理的订单是有优先级的,也就是说,不同的订单类型所要求被处理的紧迫程度不同,对那些优先级高的注单要先处理,对于优先级低的注单可稍后处理.对于处于同一优先级的订单了,就按照其到达的先后顺序进行处理.     这是一个典型的管理具有优先级的对象的需求,注单就是具有优先级(With Priority)的对象.我设计了ESBasic.ObjectManagement.Managers.IPriorityManager优先级管理器(确切地说,应该称之为"具有优