Immutable Collections(3)Immutable List实现原理(中)变化中的不变

Immutable  Collections(3)Immutable List实现原理()变化中的不变

/玄魂

前言

在上一篇文章(Immutable Collections2ImmutableList<T>实现原理.(上),分析了)ImmutableList<T>的初始化过程,本篇博客分析除初始化之外的行为,当然概括起来也很简单——添加、删除、修改。这些行为的背后,我们会看到不可变集合的不变性是如何保持的,如何在不完全拷贝的情况下返回新的集合等等特性的秘密。

博文中引用的代码并非是.NET源码,而是反编译得来,不正确之处,还望指教。

3.1  ADD

接上篇博文,当初始化一个没有任何元素的ImmutableList<T>对象之后,对象会获得一个EmptyNode

下面看看添加一个元素的流程,及数据结构的变化。

测试代码:

 static void Main(string[] args)
        {
            var fruitBasket = ImmutableList.Create<string>();
          var ass  = fruitBasket.Add("ddd");}

 

如上图,在Add方法内部,会调用Node类型的Add方法,返回一个新的的Node实例。Add方法源码如下:

                    internal ImmutableList<T>.Node Add(T key)
                    {
                           return this.Insert(this.count, key);
                    }

Add方法又调用了Insert方法,此时count=0key=”ddd”。在Insert内部先判断了左子树是否为空,如果为空则创建新的Node,调用具有四个输入参数的构造函数。

      if (this.IsEmpty)//
                           {
                                  return new ImmutableList<T>.Node(key, thisthisfalse);
                           }

这一步很巧妙的完成了树的构造,代码如下:

private Node(T key, ImmutableList<T>.Node left, ImmutableList<T>.Node right, bool frozen = false)
                    {
                           Requires.NotNull<ImmutableList<T>.Node>(left, "left");
                           Requires.NotNull<ImmutableList<T>.Node>(right, "right");
                           this.key = key;
                           this.left = left;
                           this.right = right;
                           this.height = 1 + Math.Max(left.height, right.height);
                           this.count = 1 + left.count + right.count;
                           this.frozen = frozen;
                    }

原来的rootempty node)这里变成新Node的左右子节点,新节点key字段(即value)被赋值“ddd”,heightcount都等于1,此时frozen=false。需要注意的细节是,调用Node(T key, ImmutableList<T>.Node left, ImmutableList<T>.Node right, bool frozen = false)之前传入的this指针和函数内部的this指针指向的是不同的内存区域。

注意,传入的Node对象没有做任何修改,返回的是新NewNode

当前创建的Node对象的结构如下:

 

继续运行,从Node类里出来,回到ImmutableList<T>Add方法:

      public ImmutableList<T> Add(T value)
             {
                    ImmutableList<T>.Node node = this.root.Add(value);
                    return this.Wrap(node);
             }

在得到新的的Node后,会执行Wrap方法。

 

同理,内部的Node完成了树形结构的转换,外部的ImmutableList<T>也要完成这一转换,返回新的ImmutableList<T>对象,将新的Node赋值到自己的root字段上,并初始化相关字段。

      private ImmutableList(ImmutableList<T>.Node root, IEqualityComparer<T> valueComparer)
             {
                    Requires.NotNull<ImmutableList<T>.Node>(root, "root");
Requires.NotNull<IEqualityComparer<T>>(valueComparer, "valueComparer");
                    root.Freeze();
                    this.root = root;
                    this.valueComparer = valueComparer;
             }

OK,终于又回到了Main函数中,完成了一次轮回:

 

ImmutableList<T>通过更新树结构,新建ImmutableList<T>对象同时更新对Node的引用创建新的集合。树结构虽然发生了变化,但是原来的集合对节点的引用并没有发生变化,从而保证了集合的不变性。

继续修改Main函数的代码:

  static void Main(string[] args)
        {
            var fruitBasket = ImmutableList.Create<string>();
          var a2  = fruitBasket.Add("ddd");
          var a3 = a2.Add("ccc");
        }

我们观察执行var a3 = a2.Add("ccc")时的行为变化。

 

当前代码沿着上图所示的路径再次来到Node类的Insert方法。

 

第一次执行Add时的情景上面分析过了,当Node的左右子树不为空时,首先要判断元素应该添加左还是右,判断逻辑很简单,判断当前准备添加元素的索引是否小于等于左子树元素的个数。由于当前左子树只有一个Empty节点,所以元素会被添加到右子树上去。可以设想一下,如果再次执行添加操作,元素还是会被添加到右子树上去,左边会一直为空。所以在每次添加操作执行完毕之后,会调用MakeBalanced方法来使左右平衡。如果您熟悉红黑树的话,对保持树的平衡时使用的扭转算法应该不会陌生。这里我不想深入解释ImmutableList 的“平衡扭转”算法,我觉得单独拿出来一篇博文来讲解会更好。

下一篇博客中,我们继续分析ImmutableList的其他行为的原理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值