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

文/玄魂

前言

在上一篇文章(Immutable Collections(2)ImmutableList<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=0,key=”ddd”。在Insert内部先判断了左子树是否为空,如果为空则创建新的Node,调用具有四个输入参数的构造函数。

if (this.IsEmpty)//

{

return new ImmutableList<T>.Node(key, this, this, false);

}

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

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;

}

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

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

当前创建的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的其他行为的原理。

Immutable Collections(3)Immutable List实现原理(中)变化中的不变的更多相关文章

  1. 不可变集合 Immutable Collections

    例子 public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of( "red", &q ...

  2. Ajax跨域原理及JQuery中的实现

    浅析Ajax跨域原理及JQuery中的实现分析   AJAX 的出现使得网页可以通过在后台与服务器进行少量数据交换,实现网页的局部刷新.但是出于安全的考虑,ajax不允许跨域通信.如果尝试从不同的域请 ...

  3. 【转】JVM运行原理及JVM中的Stack和Heap的实现过程

    来自: http://blog.csdn.net//u011067360/article/details/46047521 Java语言写的源程序通过Java编译器,编译成与平台无关的‘字节码程序’( ...

  4. [Immutable.js] Transforming Immutable Data with Reduce

    Immutable.js iterables offer the reduce() method, a powerful and often misunderstood functional oper ...

  5. gdb调试原理及qemu中的gdbserver

    (一)gdb调试原理 此部分转自:https://blog.csdn.net/u012658346/article/details/51159971     https://www.cnblogs.c ...

  6. jquery中ajax中post方法(多学习:洞悉原理,触类旁通)(函数封装思想)

    jquery中ajax中post方法(多学习:洞悉原理,触类旁通)(函数封装思想) 一.总结 1.多看学习视频:洞悉原理,触类旁通, 2.函数封装:$.post(URL,data,callback); ...

  7. 深入浅出-iOS Block原理和内存中位置

    Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0 #简介 今天回顾一下blcok,基本 ...

  8. 【转】QT中QDataStream中浮点数输出问题

    先上代码: C/C++ code   ? 1 2 3 4 5 6 7 8 9 10 11 12 13 int main(int argc, char *argv[]) {     QApplicati ...

  9. Firebug中调试中的js脚本中中文内容显示为乱码

    Firebug中调试中的js脚本中中文内容显示为乱码 设置 页面 UFT-8 编码没用, 解决方法:点击 "Firebug"工具栏 中的"选项"---" ...

随机推荐

  1. /Library,/System/Library,~/Library

      /System/library是系统级,/ Library下面面向全部用户,~/Library 限于当前用户 一般来说,很少碰/ Library,都是用到/System/library和~/lib ...

  2. gorm中数据库datetime类型的映射和time.Time的格式化

    如果在结构体中设置time变量的类型是time.Time,那么gorm取出来的时间格式将会是”2006-01-02 15:04:05.999999999 -0700 MST“东八区时间,在time.T ...

  3. Spring简单获得实体类的实例, 使用ApplicationContext()方法的几点注意事项

    今天接触了Spring的初步用法, 感觉跟实例化实体类没啥区别, 像这种简单的代码还不如直接实例化来的方便, 这样使用Spring的话总共需要三个文件 第一个当然是一个实体类了, 定义好属性, get ...

  4. 自定义 tableviewheader 高度显示不正常

    BUG : 自定义的Xib View, 设置为tableview分区头的时候  tableview 如果是plain 模式, 设置 sectionHeader,  sectionHeader会成为悬浮 ...

  5. 通过yiic来创建yii应用

    一.通过yiic来创建yii应用 (*yiic命令在yii下载包的framework目录下) 1.把你自已的php环境添加到系统环境变量中. 2.在命令行下输入: yiic webapp 位置\名称 ...

  6. Codeforces 677C. Vanya and Label 位操作

    C. Vanya and Label time limit per test:1 second memory limit per test:256 megabytes input:standard i ...

  7. Geodetic集合

    图G是一个无向连通图,没有自环,并且两点之间至多只有一条边.我们定义顶点v,u最短路径就是从v到u经过边最少的路径.所有包含在v-u的最短路径上的顶点被称为v-u的Geodetic顶点,这些顶点的集合 ...

  8. FEATURE_MCT_READERDIRECT问题

    刚才查了一下,好像与一个叫CCID的驱动有关.你把FEATURE_MCT_READEDIRECT定义成0x08,再make一下试试.

  9. [SoapUI] 设置最大等待时间,不断重复的去发送一个request,每次从response中获取一个status,直到这个status从一种状态变成另外一种状态

    import com.eviware.soapui.support.GroovyUtils def groovyUtils = new GroovyUtils( context ) def holde ...

  10. 深入浅出 JMS(四) - ActiveMQ 消息存储

    深入浅出 JMS(四) - ActiveMQ 消息存储 一.消息的存储方式 ActiveMQ 支持 JMS 规范中的持久化消息与非持久化消息 持久化消息通常用于不管是否消费者在线,它们都会保证消息会被 ...