一、存储图解
我以下面这段代码为例子,画出这个List的存储构造:
let myList = [];
for(let i=0;i<1100;i++) {
myList[i] = i;
}
debugger;//能够在这里打个断点调试
let immutableList = Immutable.List(myList)
debugger;
console.log(immutableList.set(1000, 'Remm'));
debugger;
console.log(immutableList.get(1000));
二、vector trie 的构建历程
我们用上面的代码为例子一步一步的剖析。首先是把原生的list转换为inmutable的list 范例:
export class List extends IndexedCollection {
// @pragma Construction
constructor(value) { // 此时的value就是上面的myList数组
const empty = emptyList();
if (value === null || value === undefined) {//推断是不是为空
return empty;
}
if (isList(value)) {//推断是不是已是imutable的list范例
return value;
}
const iter = IndexedCollection(value);//序列化数组
const size = iter.size;
if (size === 0) {
return empty;
}
assertNotInfinite(size);
if (size > 0 && size < SIZE) { // 推断size是不是凌驾32
return makeList(0, size, SHIFT, null, new VNode(iter.toArray()));
}
return empty.withMutations(list => {
list.setSize(size);
iter.forEach((v, i) => list.set(i, v));
});
}
。。。。。。
}
首先会建立一个空的list
let EMPTY_LIST;
export function emptyList() {
return EMPTY_LIST || (EMPTY_LIST = makeList(0, 0, SHIFT));
}
SHIFT的值为5,export const SHIFT = 5; // Resulted in best performance after ______?
再继承看makeList,能够清楚看到 List 的主要部份:
function makeList(origin, capacity, level, root, tail, ownerID, hash) {
const list = Object.create(ListPrototype);
list.size = capacity - origin;// 数组的长度
list._origin = origin;// 数组的肇端位置 平常是0
list._capacity = capacity;// 数组容量 即是 size
list._level = level;//树的深度,为0时是恭弘=叶 恭弘子结点。默认值是5,存储指数部份,用于轻易位运算,增添一个深度,level值+5
list._root = root;// trie树完成
list._tail = tail;// 32个为一组,寄存末了盈余的数据 实在就是 %32
list.__ownerID = ownerID;
list.__hash = hash;
list.__altered = false;
return list;
}
将传入的数据序列化
// ArraySeq
iter = {
size: 数组的length,
_array: 传入数组的援用
}
推断size是不是凌驾32,size > 0 && size < SIZE
这里 SIZE : export const SIZE = 1 << SHIFT;
即 32。若没有凌驾32,一切数据都放在_tail中。
_root 和 _tail 内里的数据又有以下构造:
// @VNode class
constructor(array, ownerID) {
this.array = array;
this.ownerID = ownerID;
}
能够如许调试检察:
let myList = [];
for(let i=0;i<30;i++) {
myList[i] = i;
}
debugger;//能够在这里打个断点调试
console.log(Immutable.List(myList));
size假如凌驾32
return empty.withMutations(list => {
list.setSize(size);//构建立的构造 重如果盘算出树的深度
iter.forEach((v, i) => list.set(i, v));//添补好数据
});
export function withMutations(fn) {
const mutable = this.asMutable();
fn(mutable);
return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this;
}
list.setSize(size)
中有一个主要的要领setListBounds
,下面我们主要看这个要领怎样构建这颗树
这个要领最主要的作用是 肯定 list的level
function setListBounds(list, begin, end) {
......
const newTailOffset = getTailOffset(newCapacity);
// New size might need creating a higher root.
// 是不是须要增添数的深度 把 1 左移 newLevel + SHIFT 位 相当于 1 * 2 ^ (newLevel + SHIFT)
// 以 size为 1100 为例子 newTailOffset的值为1088 第一次 1088 > 2 ^ 10 树增添一层深度
// 第二次 1088 < 2 ^ 15 跳出轮回 newLevel = 10
while (newTailOffset >= 1 << (newLevel + SHIFT)) {
newRoot = new VNode(
newRoot && newRoot.array.length ? [newRoot] : [],
owner
);
newLevel += SHIFT;
}
......
}
function getTailOffset(size) {
// (1100 - 1) / 2^5 % 2^5 = 1088
return size < SIZE ? 0 : (((size - 1) >>> SHIFT) << SHIFT);
}
经由 list.setSize(size);
构建好的构造
三、set 要领
iter.forEach((v, i) => list.set(i, v));
这里是将iter中的_array添补到list
这里主要照样看看set要领怎样设置数据
set(index, value) {
return updateList(this, index, value);
}
function updateList(list, index, value) {
......
if (index >= getTailOffset(list._capacity)) {
newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter);
} else {
newRoot = updateVNode(
newRoot,
list.__ownerID,
list._level,
index,
value,
didAlter
);
}
......
}
function updateVNode(node, ownerID, level, index, value, didAlter) {
// 依据 index 和 level 盘算 数据set的位置在哪
const idx = (index >>> level) & MASK;
// 应用递归 一步一步的寻觅位置 直到找到终究的位置
if (level > 0) {
const lowerNode = node && node.array[idx];
const newLowerNode = updateVNode(
lowerNode,
ownerID,
level - SHIFT,
index,
value,
didAlter
);
......
// 把node节点的array复制一份天生一个新的节点newNode editableVNode函数见下面源码
newNode = editableVNode(node, ownerID);
// 回溯阶段将 子节点的援用赋值给本身
newNode.array[idx] = newLowerNode;
return newNode;
}
......
newNode = editableVNode(node, ownerID);
// 当递归到恭弘=叶 恭弘子节点 也就是level <= 0 将值放到这个位置
newNode.array[idx] = value;
......
return newNode;
}
function editableVNode(node, ownerID) {
if (ownerID && node && ownerID === node.ownerID) {
return node;
}
return new VNode(node ? node.array.slice() : [], ownerID);
}
下面我们看看运行了一次set(0,0)
的效果
全部构造构建完以后
下面我们接着看方才我们构建的list set(1000, 'Remm')
,实在一切的set的源码上面已剖析过了,我们再来复习一下。
挪用上面的set要领,index=1000,value='Remm'
。挪用updateList,继而挪用updateVNode。经由过程const idx = (index >>> level) & MASK;
盘算要寻觅的节点的位置(在这个例子中,idx的值依次是0->31->8)。 不停的递归查找,当 level <= 0 抵达递归的停止前提,实在就是到达树的恭弘=叶 恭弘子节点,此时经由过程newNode = editableVNode(node, ownerID);
建立一个新的节点,然后 newNode.array[8] = 'Remm'
。接着就是最先回溯,在回溯阶段,本身把本身克隆一个,newNode = editableVNode(node, ownerID);
,注重这里克隆的只是援用,所以不是深拷贝。然后再将idx位置的更新了的子节点从新赋值,newNode.array[idx] = newLowerNode;
,如许沿着途径一向返回,更新途径上的每一个节点,末了获得一个新的根节点。
更新后的list:
四、get 要领
相识完上面的list构建和set,我们再来看 immutableList.get(1000)
源码就是小菜一碟了。
get(index, notSetValue) {
index = wrapIndex(this, index);
if (index >= 0 && index < this.size) {
index += this._origin;
const node = listNodeFor(this, index);
return node && node.array[index & MASK];
}
return notSetValue;
}
function listNodeFor(list, rawIndex) {
if (rawIndex >= getTailOffset(list._capacity)) {
return list._tail;
}
if (rawIndex < 1 << (list._level + SHIFT)) {
let node = list._root;
let level = list._level;
while (node && level > 0) {
// 轮回查找节点所在位置
node = node.array[(rawIndex >>> level) & MASK];
level -= SHIFT;
}
return node;
}
}
五、tire 树 的长处
来一张从网上盗来的图:
这种树的数据构造(tire 树),保证其拷贝援用的次数降到了最低,就是经由过程极度的体式格局,大大下降拷贝数目,一个具有100万条属性的对象,浅拷贝须要赋值 99.9999万次,而在 tire 树中,依据其接见的深度,只要一个层级只须要拷贝 31 次,这个数字不跟着对象属性的增添而增大。而跟着层级的深切,会线性增添拷贝数目,但由于对象接见深度不会特别高,10 层已险些见不到了,因而最多拷贝300次,速率照样非常快的。
我上面所剖析的状况有 构建、修正、查询。实在另有 增加 和 删除。
实在Immutable.js 部份参考了 Clojure 中的PersistentVector的完成体式格局。所以能够看看下面这篇文章: