Erlang数据类型底层实现剖析 三

这回我们看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
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值