Immutable 源码解析 - List 对象

什么是 Trie

  • 查询每个条目的复杂度跟树中的元素个数无关
  • 查询复杂度只跟深度有关 O(deep)
  • 修改某个节点可以轻松记录查找的路径

Immutable.List 的 Trie

  • Immutable.List 的 Trie 结构是一个 32 叉宽的结构


List 构建

// 示例代码,创建一个 1100 长度的 list
const Immutable = require('immutable')
let arr = [];
for (let i = 0; i < 1100; i++) {
  arr[i] = i;
}
const list = Immutable.List(arr)
debugger;
console.log(list)

创建空的 List 对象


只有在创建空 List 时才调用 makeList,这时的 list 才会有 ownerID 属性, lilst.ownerID 用于标识需要进行 VNode 对象引用比较,VNode 的 ownerID 用于判断引用 === ,在初始化空 list 时防止多次创建,list 创建完毕后会删除 list.ownerID,再次更新 list 时就必须返回新的 VNode 变为可变对象

// 生成一个空的 List 对象
  var EMPTY_LIST;
  function emptyList() {
    return EMPTY_LIST || (EMPTY_LIST = makeList(0, 0, SHIFT));
  }

  function makeList(origin, capacity, level, root, tail, ownerID, hash) {
    var list = Object.create(ListPrototype);
    list.size = capacity - origin;
    list._origin = origin;
    list._capacity = capacity;
    list._level = level;
    list._root = root;
    list._tail = tail;
    list.__ownerID = ownerID; // 只在空 list 初始化时存在
    list.__hash = hash;
    list.__altered = false;
    return list;
  }

设置 List 的边界

根据传入数组的长度来决定如何构建树的深度和结构

setListBounds 根据 List 的 size 设置容量、_root、_tail 的信息

描述 trie 树尺寸的常量

  // Constants describing the size of trie nodes.
  var SHIFT = 5; 
  var SIZE = 1 << SHIFT; // 32
  var MASK = SIZE - 1; // 31

创建 list 结构

function setListBounds(list, begin, end) {
  // ...

  var newCapacity = 
  var newLevel = list._level; // 初始 1 层结构是 5
  var newRoot = list._root;

  var oldTailOffset = getTailOffset(oldCapacity);
  var newTailOffset = getTailOffset(newCapacity); // 根据容量计算 _tail 的位置,offset 为 32 的倍数

  // New size might need creating a higher root. SHIFT = 5, 这里 1 <<< (5 + 5) = 1024, 也是 32 * 32 的倍数,此时一层最大就只有 1024 个,加一层结构
  while (newTailOffset >= 1 << (newLevel + SHIFT)) { // newTailOffset >= (1 << (newLevel + SHIFT))
    newRoot = new VNode(newRoot && newRoot.array.length ? [newRoot] : [], owner);
    newLevel += SHIFT; // 加一层结构
  }

    // Locate or create the new tail. // 创建 _tail
    var oldTail = list._tail;
    var newTail = newTailOffset < oldTailOffset ?
      listNodeFor(list, newCapacity - 1) :
      newTailOffset > oldTailOffset ? new VNode([], owner) : oldTail;

    // ...

    // 初始化 list 结构
    if (list.__ownerID) {
      list.size = newCapacity - newOrigin;
      list._origin = newOrigin;
      list._capacity = newCapacity;
      list._level = newLevel;
      list._root = newRoot;
      list._tail = newTail;
      list.__hash = undefined;
      list.__altered = true;
      return list;
    }
}

最后得到的 list 结构

getTailOffset 找到 tail 的起始位置

如果 size 小于 32 就是从 0 开始,size - 1 向左移 5 位再向右移 5 位,会得到一个小于 size 中倍数 32 的最大数。size = 1100, 得出 1088, 得出的数就是 tail 开始的位置

  var SHIFT = 5; 
  var SIZE = 1 << SHIFT; // 32

  function getTailOffset(size) {
    return size < SIZE ? 0 : (((size - 1) >>> SHIFT) << SHIFT);
  }

构建 _root,根据 size 决定 trie 的深度,1 << (newLevel + SHIFT),第一次 1 << 10 = 1024, 也就是 32 * 32 一层的数量,size 超过一层的空间 SHIFT + 5,增加一层深度

    // 初始 SHIFT = 5, 这里 1 <<< (5 + 5) = 1024, 也是 32 * 32 的倍数,此时一层最大就只有 1024 个,加一层结构
    while (newTailOffset >= 1 << (newLevel + SHIFT)) { // newTailOffset >= (1 << (newLevel + SHIFT))
      newRoot = new VNode(newRoot && newRoot.array.length ? [newRoot] : [], owner);
      newLevel += SHIFT; // 加一层q结构
    }

List.set

withMutations

List.set 在 withMutations 回调中可以暂时使用可变对象的操作方式,执行 updateList 方法进行赋值



withMutaions 在回调给 fn 传了一个可以进行可变操作的 list,操作完后返回新对象

export function withMutations(fn) {
  const mutable = this.asMutable();
  fn(mutable);
  return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this;
}

updateList 更新 list

  function updateList(list, index, value) {
    index = wrapIndex(list, index); // 确定好 index 

    if (index !== index) {
      return list;
    }

    if (index >= list.size || index < 0) {
      return list.withMutations(function(list ) {
        index < 0 ?
          setListBounds(list, index).set(0, value) : // 小于 0 头部加一个
          setListBounds(list, 0, index + 1).set(index, value) 
      });
    }

    index += list._origin; // 找到在 list 上的位置

    var newTail = list._tail;
    var newRoot = list._root;
    var didAlter = MakeRef(DID_ALTER);
    // 更新的 index, 在 _tail 还是 _root 中, 分别更新
    if (index >= getTailOffset(list._capacity)) { // index 在 _tail 中 更新 _tail
      newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter); // 在范围外的的用 tail 结构
    } else { // index 在 _root 中
      newRoot = updateVNode(newRoot, list.__ownerID, list._level, index, value, didAlter); // 在范围内的 root 结构
    }

    if (!didAlter.value) {
      return list;
    }
    // 初始化的时候有 ownerID 防止多次创建,创建完了后就设置为 undefined
    if (list.__ownerID) {
      list._root = newRoot;
      list._tail = newTail;
      list.__hash = undefined;
      list.__altered = true;
      return list;
    }
    return makeList(list._origin, list._capacity, list._level, newRoot, newTail);
  }

这里看下 List 结构的更新,root 为 trie 结构上,更新时传入树深度的 level,而更新 tail 这种扁平化数组结构不需要深度 level

    // 更新的 index, 在 _tail 还是 _root 中, 分别更新
    if (index >= getTailOffset(list._capacity)) { // index 在 _tail 中 更新 _tail
      newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter); // 在范围外的的用 tail 结构
    } else { // index 在 _root 中
      newRoot = updateVNode(newRoot, list.__ownerID, list._level, index, value, didAlter); // 在范围内的 root 结构
    }

updateNode

updateVNode 分两段逻辑,一段是更新 root 的 trie 树,一段是更新扁平化 VNode 数组逻辑比如 tail 结构或者是 root trie 树的最后一层。

updateNode 更新扁平化结构

通过换位找到 index 在当前层级数组上的 idx 后, tail 这种扁平化的 VNode 会通过 editableVNode 拷贝一个新的 newNode 用来更新,返回的也是这个 newNode 新的 VNode,替换掉原来的 tail 或者 root 的最下层 VNode

updateNode 更新 trie 树结构

通过换位运算来找到 index 在当前层级下的 idx 索引, 当 level > 0 时表示此时的 VNode 不是最下层,通过递归 updateVNode 找到最下一层进行操作

editableVNode

换位运算

// 十进制 index = 1000, level = 10, MASK = 31
// 二进制 MASK 011111
var idx = (index >>> level) & MASK

第一轮位运算, 得到 index 的位置在 trie 上第一层 VNode.array 的序号

// 十进制 1000, 二进制 11111 01000 向右移 10 位得到 0, 再 & MASK 还是 0 
// 十进制 1088, 二进制 1 00010 00000 向右移 10 位得到 1, 再 & MASK 是 1

第二轮位运算,level - 5 = 5 得到 index 在下一层 VNode.array 的序号

// 递归 level - 5 = 5
// 十进制 1000, 二进制 11111 01000 向右移 10 位得到 11111, 再 & MASK 还是 7
// 十进制 1088, 二进制 1 00010 00000 向右移 10 位得到 100010 , 再 & MASK 是 10 

此时 level - 5 = 0 不会进入 if (level > 0) 的递归操作中,代表已经找到了 root 中 trie 的最下一层,index 的位置,进入操作扁平化数组的逻辑中,不断返回到上一层进行路径的回溯创建一直到 root 上返回新的 root。

List.get

根据 index 找到扁平化内容的 VNode,index & MASK 结果是 0 - 31 得到序号从 VNode.array 中找到 value

listNode

先确认是不是在 tail 上,在 root 上就 level - 5 进行循环找到最后一层 VNode

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容

  • immutable 状态机 trie树 位运算快,直接对整数在内存中的二进制位进行操作,不需要转成十进制 参考文章...
    Haylee_k阅读 833评论 0 0
  • 如果你看完书中的所有例子,你很可能已经做完你的实验和在已经越狱的iPhone上的研究。因为和许多人一样,几乎所有的...
    fishmai0阅读 16,078评论 2 42
  • 声明:摘自github:https://github.com/ZtesoftCS/go-ethereum-code...
    蓝Renly阅读 777评论 0 0
  • Scala的集合类可以从三个维度进行切分: 可变与不可变集合(Immutable and mutable coll...
    时待吾阅读 5,826评论 0 4
  • 看到这个题目,《你的职场生涯,是如何一步步被毁掉的?》我回忆我上班的这几年,从11年毕业至今,工作已有7年。七年的...
    欣雅阅读阅读 161评论 0 1