这回我们看erlang的map数据结构,底层实现为hashmap时的相关处理
从hashmap_from_validated_list(BIF_P, BIF_ARG_1, size)的实现入手
static Eterm hashmap_from_validated_list(Process *p, Eterm list, Uint size) {
while(is_list(item)) {
res = CAR(list_val(item));
kv = tuple_val(res);
hx = hashmap_restore_hash(tmp,0,kv[1]); // 对value进行hash hx为32位
swizzle32(sw,hx); //对hash值进行倒转,如hx结果为 0x1234,则转为0x4321
hxns[ix].hx = sw; // 记录hash值
hxns[ix].val = CONS(hp, kv[1], kv[2]); // 记录Key,Value
hp += 2;
hxns[ix].skip = 1;
hxns[ix].i = ix;
ix++;
item = CDR(list_val(item));
}
// 将无序的hxns数值构造为hashmap
res = hashmap_from_unsorted_array(&factory, hxns, size, 0);
return res;
}
static Eterm hashmap_from_unsorted_array(ErtsHeapFactory* factory,
hxnode_t *hxns, Uint n,
int reject_dupkeys) {
Uint jx = 0, ix = 0, lx, cx;
Eterm res;
/* 对hash进行由大至小的快排*/
qsort(hxns, n, sizeof(hxnode_t), (int (*)(const void *, const void *)) hxnodecmp);
ix = 0, cx = 0;
while(ix < n - 1) {
// 对重复的hash值进行处理(即 hash碰撞处理)
if (hxns[ix].hx == hxns[ix+1].hx) {
jx = ix + 1;
// 统计碰撞长度
while(jx < n && hxns[ix].hx == hxns[jx].hx) { jx++; }
// 对hash碰撞区域中的数据,根据Key的大小重新排序 --为了更方便的进行Key值去重
qsort(&hxns[ix], jx - ix, sizeof(hxnode_t),
(int (*)(const void *, const void *)) hxnodecmpkey);
while(ix < jx) {
lx = ix;
// 如果存在相同的Key,则后者覆盖前者 如maps:from_list([{1,1},{1,2}]). 结果为 #{1 => 2}.
while(++ix < jx && EQ(CAR(list_val(hxns[ix].val)), CAR(list_val(hxns[lx].val)))) {
if (hxns[ix].i > hxns[lx].i) {lx = ix;}
}
hxns[cx].hx = hxns[lx].hx;
hxns[cx].val = hxns[lx].val;
cx++;
}
ix = jx;
continue;
}
// 有重复Key被剔除后,整理数列
if (ix > cx) {
hxns[cx].hx = hxns[ix].hx;
hxns[cx].val = hxns[ix].val;
}
cx++;
ix++;
}
// 有重复Key被剔除后,整理数列
if (ix < n) {
hxns[cx].hx = hxns[ix].hx;
hxns[cx].val = hxns[ix].val;
cx++;
}
/* 将排好序的key值唯一的hxns数列转为hashmap */
res = hashmap_from_sorted_unique_array(factory, hxns, cx, 0);
return res;
}
static Eterm hashmap_from_sorted_unique_array(ErtsHeapFactory* factory,
hxnode_t *hxns, Uint n, int lvl) {
Eterm res = NIL;
Uint i,ix,jx,elems;
Uint32 sw, hx;
Eterm val;
hxnode_t *tmp;
ix = 0;
elems = 1;
while (ix < n - 1) {
// 对hash值相同的数据进行处理
if (hxns[ix].hx == hxns[ix+1].hx) {
jx = ix + 1;
// 统计hash值相同的数列长度
while (jx < n && hxns[ix].hx == hxns[jx].hx) { jx++; }
// 申请临时内存
tmp = (hxnode_t *)erts_alloc(ERTS_ALC_T_TMP, ((jx - ix)) * sizeof(hxnode_t));
// 处理hash冲突
for(i = 0; i < jx - ix; i++) {
val = hxns[i + ix].val;
// 计算新的hash值
hx = hashmap_restore_hash(th, lvl + 8, CAR(list_val(val)));
swizzle32(sw,hx);
tmp[i].hx = sw;
tmp[i].val = val;
tmp[i].i = i;
tmp[i].skip = 1;
}
qsort(tmp, jx - ix, sizeof(hxnode_t), (int (*)(const void *, const void *)) hxnodecmp);
hxns[ix].skip = jx - ix; // 记录hash值碰撞长度
// 将新hash值的数列构造为一个hashmap
hxns[ix].val = hashmap_from_sorted_unique_array(factory, tmp, jx - ix, lvl + 8);
erts_free(ERTS_ALC_T_TMP, (void *) tmp);
ix = jx;
if (ix < n) { elems++; }
continue;
}
hxns[ix].skip = 1;
elems++;
ix++;
}
// 将没有hash冲突的hxns数列,转为hashmap
res = hashmap_from_chunked_array(factory, hxns, elems, n, !lvl);
return res;
}
/* 最终形成的hashmap是树形结构,每个节点有3种类型
01. bitmap-node 普通节点
10. array-head 根节点 -- 插槽塞满了的根节点
11. bitmap-head 根节点
hashmap树的每个节点最多有16个插槽
没有碰撞的hashmap树 或 将碰撞数据构成的子树视作一个节点的话 则树的高度最高为8
*/
static Eterm hashmap_from_chunked_array(ErtsHeapFactory *factory, hxnode_t *hxns, Uint n,
Uint size, int is_root) {
Uint ix, d, dn, dc, slot, elems;
Uint32 v, vp, vn, hdr;
Uint bp, sz;
DECLARE_ESTACK(stack); // 初始化 栈数据结构
Eterm res = NIL, *hp = NULL, *nhp;
if (n == 1) { // hxns 只有一项数据时的处理, 此处跳过分析
...
return make_hashmap(hp);
}
//将32位的hash值以每4位为一组,拆成8组,最左4位记为第0组
ix = 0;
d = 0;
vp = hxns[ix].hx;
v = hxns[ix + hxns[ix].skip].hx;
slot = maskval(vp,d); //maskval实现为 (((V) >> ((7 - (L))*4)) & 0xf),即取出第d组hash值,也可视为当前所处插槽值
while(slot == maskval(v,d)) {
ESTACK_PUSH(stack, 1 << slot);
d++;
slot = maskval(vp,d);
}
res = hxns[ix].val;
// skip>1 说明是有hash碰撞, val为碰撞的数据构成的子树
if (hxns[ix].skip > 1) {
dc = 7;
// 构造碰撞节点,因为当前hash值完全碰撞(即8组都碰撞),所以深度为8
while (dc > d) {
hp = erts_produce_heap(factory, HAMT_NODE_BITMAP_SZ(1), HALLOC_EXTRA);
/* bitmap-node 构造普通节点 hp[0]的值意义拆解:
hp[0]&0xFF00 的值为 1 << maskval(vp,dc). 该16位数从右往左数的第X位为1,表示当前节点的第X个插槽有数据
hp[0]&0x00C0 的值为 0b01, 表示节点类型为 普通节点
hp[0]&0x003F 的值为 0b111100,表示该Eterm为Map数据结构
*/
hp[0] = MAP_HEADER_HAMT_NODE_BITMAP(1 << maskval(vp,dc));
// 该节点的第一项数据的值为res 注:第一项表示第一个非空插槽,如节点前三个插槽为空,第四个插槽才有值,则第一项指第四个插槽
hp[1] = res;
res = make_hashmap(hp);
dc--;
}
}
// 整个转换流程利用了 stack 栈作中转, 栈中的数据分为两种类型,插槽占用状态(标记哪些插槽被占用 hdr,slot,hdr | bp等) 和 插槽中放置的数据(res)
ESTACK_PUSH2(stack,res,1 << slot);
elems = n - 2;
// 处理中间节点(除去第一个节点和最后一个节点)
while(elems--) {
hdr = ESTACK_POP(stack);
ix = ix + hxns[ix].skip;
vn = hxns[ix + hxns[ix].skip].hx;
// dn 为 v,vn hash值开始出现差异时的组数
dn = cdepth(v,vn);
res = hxns[ix].val;
if (hxns[ix].skip > 1) {
int wat = (d > dn) ? d : dn;
dc = 7;
while (dc > wat) {
hp = erts_produce_heap(factory, HAMT_NODE_BITMAP_SZ(1), HALLOC_EXTRA);
hp[0] = MAP_HEADER_HAMT_NODE_BITMAP(1 << maskval(v,dc));
hp[1] = res;
res = make_hashmap(hp);
dc--;
}
}
if (d < dn) {
// d < dn 的例子
// hxns[0].hx 为 0xFFFFFFFF (skip均为1) hxns[0].val 为 0x0
// hxns[1].hx 为 0xFFFFDFFF hxns[1].val 为 0x1
// hxns[2].hx 为 0xFFFFDFDF hxns[2].val 为 0x2
// 则此时 d = 4, ix = 1, v = hxns[1].hx, vn = hxns[2].hx, dn = 6, hdr = 1 << 16
// POP hdr前stack栈中数据为 [1<<16,1<<16,1<<16,1<<16,0x0,1<<16] 栈中数据含义为:把hxns[0].val插入到了
// 根节点(第0层)第十六个插槽指向的节点(第1层)的第十六个插槽指向的节点(第2层)的第十六个插槽指向的节点(第3层)的第十六个插槽指向的节点(第4层)中
// d<dn,说明hxns[1]和hxns[2]仍有hash碰撞,要往树的根深处插值
while(d < dn) {
slot = maskval(v, d);
bp = 1 << slot;
// 往stack栈中压入hash信息,即要占用哪个插槽
ESTACK_PUSH(stack, hdr | bp);
d++;
hdr = 0;
}
slot = maskval(v, d);
bp = 1 << slot;
ESTACK_PUSH2(stack,res,bp);
// 插入hxns[1]后stack中的数据为 [1<<16,1<<16,1<<16,1<<16,0x0,1<<16|1<<14,1<<16,0x1,1<<16]
// 栈中数据含义:根节点(第0层)第十六个插槽指向的节点(第1层)的第十六个插槽指向的节点(第2层)的第十六个插槽指向的节点(第3层)的第十六个插槽指向的节点(第4层)中
// 第十六个插槽的数据为 0x0(即 hxns[0].val)
// 第十四个插槽指向的节点(第5层)的第十六个插槽指向的节点(第6层)的第十六个插槽的数据为 0x1(即 hxns[1].val)
} else if (d == dn) {
// d==dn,说明hxns[1]和hxns[2]没有hash碰撞了,直接将hxns[2]插入到maskval(v, d)插槽中即可
slot = maskval(v, d);
bp = 1 << slot;
ESTACK_PUSH2(stack,res,hdr | bp);
} else {
// d > dn 的例子
// hxns[0].hx 为 0xFFFFFFFF (skip均为1) hxns[0].val 为 0x0
// hxns[1].hx 为 0xFFFFDFFF hxns[1].val 为 0x1
// hxns[2].hx 为 0xFFFDFDFF hxns[2].val 为 0x2
// 则此时 d = 4, ix = 1, v = hxns[1].hx, vn = hxns[2].hx, dn = 3, hdr = 1 << 16
// POP hdr前stack栈中数据为 [1<<16,1<<16,1<<16,1<<16,0x0,1<<16] 栈中数据含义为:把hxns[0].val插入到了
// 根节点(第0层)第十六个插槽指向的节点(第1层)的第十六个插槽指向的节点(第2层)的第十六个插槽指向的节点(第3层)的第十六个插槽指向的节点(第4层)中
// d>dn,说明hxns[1]和hxns[2]的hash碰撞深度小于当前的深度,需要把当前节点的数据封装为bitmap-node,把当前的深度d收缩到dn
while (dn != d) {
// 构建子树逻辑,后面还会出现
slot = maskval(v, d);
bp = 1 << slot;
hdr |= bp;
sz = hashmap_bitcount(hdr);
hp = erts_produce_heap(factory, HAMT_NODE_BITMAP_SZ(sz), HALLOC_EXTRA);
nhp = hp;
*hp++ = MAP_HEADER_HAMT_NODE_BITMAP(hdr);
*hp++ = res; sz--;
while (sz--) { *hp++ = ESTACK_POP(stack); }
ASSERT((hp - nhp) < 18);
res = make_hashmap(nhp);
hdr = ESTACK_POP(stack);
d--;
}
ESTACK_PUSH2(stack,res,hdr);
// 收缩完毕后stack中的数据为 [1<<16,1<<16,1<<16,指向子树的res,1<<16]
// 栈中数据含义:根节点(第0层)第十六个插槽指向的节点(第1层)的第十六个插槽指向的节点(第2层)的第十六个插槽指向的节点(第3层)的第十六个插槽指向数据
// 是 封装为bitmap-node的子树。
}
vp = v;
v = vn;
d = dn;
}
// 处理最后一个节点
dn = cdepth(vp,v);
ix = ix + hxns[ix].skip;
res = hxns[ix].val;
if (hxns[ix].skip > 1) {
dc = 7;
while (dc > dn) {
hp = erts_produce_heap(factory, HAMT_NODE_BITMAP_SZ(1), HALLOC_EXTRA);
hp[0] = MAP_HEADER_HAMT_NODE_BITMAP(1 << maskval(v,dc));
hp[1] = res;
res = make_hashmap(hp);
dc--;
}
}
// 将stack中数据封装为hashmap树的流程
hdr = ESTACK_POP(stack);
// 设 hdr值为 1<<a|1<<b 则说明stack顶部两个数据分别要放置到当前节点的a插槽和b插槽中
while (dn) {
slot = maskval(v, dn);
bp = 1 << slot;
hdr |= bp;
sz = hashmap_bitcount(hdr); // 即计算hdr二进制数值中1的个数(设为n),代表的当前节点有数据的插槽个数
hp = erts_produce_heap(factory, HAMT_NODE_BITMAP_SZ(sz), HALLOC_EXTRA);
nhp = hp;
// 可得bitmap-node的内存布局
// hp[0]的含义见前文, hp[1]至hp[n+1]存放n项数据
*hp++ = MAP_HEADER_HAMT_NODE_BITMAP(hdr);
*hp++ = res; sz--;
while (sz--) { *hp++ = ESTACK_POP(stack); }
res = make_hashmap(nhp);
hdr = ESTACK_POP(stack);
dn--;
}
slot = maskval(v, dn);
bp = 1 << slot;
hdr |= bp;
sz = hashmap_bitcount(hdr);
hp = erts_produce_heap(factory, sz + (is_root ? 2 : 1), 0);
nhp = hp;
if (is_root) {
// 根节点插槽放满了,则为array-head类型,否则为bitmap-head类型
// 根节点hp[0]布局和普通节点一致
// hp[1] 为整个hashmap的key数量
// hp[2] 到 hp[n+2] 存放n项数据
*hp++ = (hdr == 0xffff) ? MAP_HEADER_HAMT_HEAD_ARRAY : MAP_HEADER_HAMT_HEAD_BITMAP(hdr);
*hp++ = size;
} else {
*hp++ = MAP_HEADER_HAMT_NODE_BITMAP(hdr);
}
*hp++ = res; sz--;
while (sz--) { *hp++ = ESTACK_POP(stack); }
res = make_hashmap(nhp);
return res;
}
假设我们有以下数据,要构造成hashmap(用16位做演示,32位类推即可)
[{1,1},{2,2},{3,3},{4,4},{5,5}]
其hash值分别如下:
1:0xFFFF
2:0xFDFF
3:0xDFDF 二次hash值: 0xEEEE
4:0xDFDF 二次hash值: 0xBEEE
5:0xDFDD