immutable
状态机
trie树
位运算快,直接对整数在内存中的二进制位进行操作,不需要转成十进制
参考文章:http://www.hypirion.com/musings/understanding-persistent-vector-pt-1 系列
对数组的处理 vector trie
trie树
最大度为32的trie树,存储在_root里
不足32的放到_tail 里
update过程
向下遍历tree,并保存路径,直到找到包含该数据的叶子结点为止,然后拷贝该叶子结点的所有数据,生成一个新的对象,再沿路径返回,拷贝所有路径上的结点,更新引用
append过程
三个边界条件:
1.最右叶子结点有空余
2.最右叶子结点无空余,但根结点有空余
3.最有叶子结点无空余,根结点无空余
pop过程
1.最右侧结点有多余一个结点
2.最后侧结点只有一个结点
1.当size是32的幂次方时,再插入一个数据会产生跟结点溢出。深度+1,初始化一个新的tail
调用updateVNode更新
- 先创建一个空的immutable List对象
emptyList()
_capacity = Array.size
_level = 每次-5,树的深度,为0时是叶子结点。默认值是5,存储指数部分,用于方便位运算
_origin?
_tail = 32个为一组,存放最后剩余的数据
_root = trie树实现
_altered
_hash
_ownerID: 新创建的节点ownerID总为undefined,表示结点之间的引用关系
需要更新的时候,将ownerId置为空对象,并在寻找叶子结点的过程中将中间结点的ownerId的引用都改成该对象 ??
将传入的数据序列化
// ArraySeq
iter = {
size: 数组的length,
_array: 传入数组的引用
}将array分组,保存到_root 及_tail中
empty.withMutations(function (list) {
// 根据size初始化树 -> setListBounds
list.setSize(size);
// 遍历ArraySeq,并向树中填充数据 -> updateList
iter.forEach(function (v, i) { return list.set(i, v); });
})
setListBounds方法
setListBounds
// 根据newTailOffset,判断是否需要增加树的深度,默认为2。(1 << 5 + 5) = 1024
while (newTailOffset >= 1 << newLevel + SHIFT) {
// 初始化增加的节点
newRoot = new VNode(
newRoot && newRoot.array.length ? [newRoot] : [],
owner
);
newLevel += SHIFT;
}
// Merge Tail into tree.
// 当新加入一个元素,并且加入后恰好超过32个元素
if (
oldTail &&
newTailOffset > oldTailOffset &&
newOrigin < oldCapacity &&
oldTail.array.length
) {
// 因为需要更新跟结点,所以需要复制root
newRoot = editableVNode(newRoot, owner);
var node = newRoot;
// 往下找,直到找到叶子结点的父结点,将tail插入末尾
for (var level = newLevel; level > SHIFT; level -= SHIFT) {
var idx = oldTailOffset >>> level & MASK;
node = (node.array[idx] = editableVNode(node.array[idx], owner));
}
node.array[oldTailOffset >>> SHIFT & MASK] = oldTail;
}
updateList方法
找出trailOffset位置:size - 1 >>> SHIFT << SHIFT
,
- SIZE=32 32为一组
SHIFT=5 2^5次方是32
::size - 1 >>> SHIFT << SHIFT::
// 每32个为一组,拿到最末尾的起始位置
function getTailOffset(size) {
return size < SIZE ? 0 : size - 1 >>> SHIFT << SHIFT;
}
size - 1 >>> SHIFT << SHIFT 先无符号右移,再有符号左移
找到下一个数组元素开始的位置。
eg: size=33 -> 33 -1 >>> 5 << 5 = 32
size=65 -> 65 - 1 >>> 5 << 5 = 32
size=68 -> 68 - 1 >>> 5 << 5 = 64
找到的位置总是2^5的倍数
updateList
// 若index大于等于size,则调用setListBounds处理边界问题、更新tree node,再将数据set进对应位置
if (index >= list.size || index < 0) {
return list.withMutations(function (list) {
index < 0
? setListBounds(list, index).set(0, value)
: setListBounds(list, 0, index + 1).set(index, value);
});
}
// 若index小于size,则分情况update tree node
if (index >= getTailOffset(list._capacity)) {
// 将数据保存在_trail中
newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter);
} else {
// 将数据保存在_root中
newRoot = updateVNode(
newRoot,
list.__ownerID,
list._level,
index,
value,
didAlter
);
}
updateVNode 方法
index >>> level & MASK
updateVNode
// 找到index在第几层的位置
// level是5的倍数。level=10是第三层,5是第一层,0是最后一层叶子结点
var idx = index >>> level & MASK;
if (level > 0) {
不断调用updateVNode寻找下一层节点
}
// 浅copy所有node.array,并生成一个新的vNode对象
newNode = editableVNode(node, ownerID);
// 更新
newNode.array[idx] = value;
return newNode
- 拿到rowIndex数据
listNodeFor方法可以找到当前index属于叶子结点的第几组,并返回list
根据rowIndex拿到rowIndex存在的列表
::1 << list._level + SHIFT::
::rawIndex >>> level & MASK::
// ListNodeFor
// 判断index是否属于tail部分
if (rawIndex >= getTailOffset(list._capacity)) {
// 返回_tail中的数据
}
// 判断index是否属于root部分
// eg:如果size=10000,_level=10, 则index肯定在2^10 ~ 2^11次方之间
if (rawIndex < 1 << list._level + SHIFT) {
var node = list._root;
var level = list._level;
// level的变化10 -> 5 -> 0
while (node && level > 0) {
// 从根节点开始深度优先遍历找每一层
node = node.array[rawIndex >>> level & MASK];
level -= SHIFT;
}
// 返回_root中叶子结点
return node;
// rawIndex >>> level & MASK得到第几个组(32个为一组)
// eg: 23 >>> 5 & 31 = 0 (在第0组里)
// 32 >>> 5 & 31 = 1 (在第1组里)
}
根据列表找到具体的rowIndex数据
// eg: index = 45 则 45 & 31 = 13,即在第13位
node.array[index & MASK]
对Map的处理 HAMT
MAX_ARRAY_MAP_SIZE=8 一个ArrayMapNode里最多放8个entities
经过Map()包装,会成为一个ArrayMapNode类的实例
immutable.Map({
‘a’: ‘value’
})
当size>8时,key会被hash化,Hashing strings · jsPerf
构建BitmapIndexedNode第九个元素会放到这里,之后把前8个遍历一遍都转化成放到这里,剩下的都是在bitmapNode上update
ValueNode 叶子结点,即数据存储位置
BitmapIndexedNode 子结点,压缩后的hashtable,头部增加bitmap用于表示压缩后元素的位置
ArrayMapNode 数组,在size<8时的数据结构
HashArrayMapNode hashtable
HashCollisionNode 用于存放hash碰撞后的ValueNode
- 判断是否是Map
如果不是:创建一个新的Map
size: 0,
_root: undefined,
__ownerId = undefined,
__hash = undefined,
__altered: false,
…其他Map定义的方法及prototype
- 序列化map
function keyedSeqFromValue(value) {
var seq = Array.isArray(value)
? new ArraySeq(value)
: isIterator(value)
? new IteratorSeq(value)
: hasIterator(value) ? new CollectionSeq(value) : undefined;
if (seq) {
return seq.fromEntrySeq();
}
if (typeof value === 'object') {
return new ObjectSeq(value);
}
throw new TypeError(
'Expected Array or collection object of [k, v] entries, or keyed object: ' +
value
);
ArrayMapNode?
当size大于8时,用BitmapIndexedNode
MergeIntoNodes
// 根据keyHash计算idx, idx在1-31之间
var idx1 = (shift === 0 ? node.keyHash : node.keyHash >>> shift) & MASK;
var idx2 = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;
...
// 总是根据id顺序放
idx1 < idx2
? [node, newNode]
: [newNode, node])
return new BitmapIndexedNode(ownerID, 1 << idx1 | 1 << idx2, nodes);
BitmapIndexedNode.prototype.update
// hash(key)
if (keyHash === undefined) {
keyHash = hash(key);
}
// 根据BitmapIndexedNode中存储的bitmap判断当前传入的key是否在某个位置已经存在。bitmap为00001010(一共有32位,这里只做为实例显示几位),其中二进制为1的表示元素存在
// 例如:keyHash 转换为二进制后为11101110000110000001101000001 ,每5位为一组,shift假定为5
// (keyHash >>> shift)& MASK 取出需要的5位,结果为26
// 1 << keyHashFrag 除第26位外,其他位都为0
// bit & bitmap 得出bitmap的第26位是否为1
var keyHashFrag = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;
var bit = 1 << keyHashFrag;
var bitmap = this.bitmap;
var exists = (bitmap & bit) !== 0;
// 省略其他代码...
// 计算1的数量,即算出key在BitmapIndexedNode的存储位置
// eg:101101,idx为4
var idx = popCount(bitmap & bit - 1);
// 如果这个位置有数据,取出当前BitmapIndexedNode中对应的数据,如果不存在,置为undefined
var nodes = this.nodes;
var node = exists ? nodes[idx] : undefined;
// 更新node
// BitmapIndeedNode情况
// 1.如果两个key有hash碰撞,则new HashCollisionNode
// 2.通过keyhash算出idx的大小,按大小顺序生成一个
BitmapIndexedNode
// undefined情况
// 直接生成一个新的ArrayNdoe
var newNode = updateNode(
node,
ownerID,
shift + SHIFT,
keyHash,
key,
value,
didChangeSize,
didAlter
);
// ...
var isEditable = ownerID && ownerID === this.ownerID;
// 生成新的Bitmap
var newBitmap = exists ? newNode ? bitmap : bitmap ^ bit : bitmap | bit;
// 生成新的nodes
// eg:exists=false, idx=1情况:
// oldArray: [vA, vC, vD]
// newArray: [vA, newVNode, vC, vD]
// exits=true情况,idx=8
// 原来位置8指向新生成的BitmapIndexedNode
var newNodes = exists
? newNode
? setIn(nodes, idx, newNode, isEditable)
: spliceOut(nodes, idx, isEditable)
: spliceIn(nodes, idx, newNode, isEditable);
当BitmapIndexedNode.nodes.length > 16时,用HashArrayMapNode,即未压缩之前的hashtable
HashArrayMapNode
HashArrayMapNode.prototype.update
// hash(key)
if (keyHash === undefined) {
keyHash = hash(key);
}
// 第一次寻找时shift为0,keyHash & MASK取低5位,找到下一层结点的位置
const idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;
const removed = value === NOT_SET;
const nodes = this.nodes;
const node = nodes[idx];
if (removed && !node) {
return this;
}
// 到下一层中寻找 shift + 5,再向左取5位
// 按BitMapIndexedNode或HashArrayMapNode继续寻找
// 如果node是ValueNode,则进入MergeIntoNodes逻辑
// 如果node是unefined,则会创建一个valueNode挂在当前HashArrayMapNode上
const newNode = updateNode(
node,
ownerID,
shift + SHIFT,
keyHash,
key,
value,
didChangeSize,
didAlter
);