erlang中dict的实现

来源:互联网 发布:ios软件开发教学 编辑:程序博客网 时间:2024/06/16 03:18
首先来看dict定义:
-define(seg_size, 16).-define(max_seg, 32).-define(expand_load, 5).-define(contract_load, 3).-define(exp_size, (?seg_size * ?expand_load)).-define(con_size, (?seg_size * ?contract_load)).-record(dict, {size=0     :: non_neg_integer(),   % Number of elements  n=?seg_size     :: non_neg_integer(),   % Number of active slots  maxn=?seg_size     :: non_neg_integer(),% Maximum slots  bso=?seg_size div 2  :: non_neg_integer(),   % Buddy slot offset  exp_size=?exp_size   :: non_neg_integer(),   % Size to expand at  con_size=?con_size   :: non_neg_integer(),   % Size to contract at  empty     :: tuple(),% Empty segment  segs     :: segs(_, _)     % Segments }).

n: 活跃的slot的数量 
maxn: 最大slot数量
bso: 这个字段很让人疑惑,通过代码知道其值为maxn/2
exp_size: 当字典中元素个数超过这个值时,字典需要扩展。
con_size: 当字典中元素个数少于这个值时,字典需要压缩(减少slots的数量)
empty: 作为扩展segs时的初始化的默认值,
segs:  真正储存数量的地方, 初始结构为{seg},每经过一次扩展,seg的数量翻倍,  
       seg的结构为{[],[],...},元组中列表的个数为?seg_size定义的大小, 这里的列表叫做bucket

何时扩展字典
字典的扩展肯定是在增加新元素时,找到dict:store/3的定义 
store(Key, Val, D0) ->     Slot = get_slot(D0, Key), %计算slot    {D1,Ic} = on_bucket(fun (B0) -> store_bkt_val(Key, Val, B0) end,   D0, Slot), %储存元素    maybe_expand(D1, Ic). %扩展字典

上面代码主要有3个函数
get_slot/2 计算slot,主要是根据hash值来计算
on_bucket/3完成了元素的存储,如果元素是新增的, 则返回的参数Ic为1,否则为0.
maybe_expand/2函数完成扩展字典的功能

get_slot函数的实现值得一提
get_slot(T, Key) ->    H = erlang:phash(Key, T#dict.maxn),    if         H > T#dict.n -> H - T#dict.bso; %另人疑惑的地方         true -> H    end.

当hash值大于活跃slot数量时,需要对结果做一个偏移,后面将能看到bso = maxn/2, n的值总是这样的 maxn>=n>bso
从这里可以推测出,当maxn变化时hash值需要重新计算, 当n的变化时,对于变化的部分也需要重新计算

现在来看字典扩展的部分,mybe_expand_aux/2(maybe_expand/2真正调用的函数)
maybe_expand_aux(T0, Ic) when T0#dict.size + Ic > T0#dict.exp_size ->    T = maybe_expand_segs(T0),% 是否需要扩展seg数量    N = T#dict.n + 1,% 活跃的slot数量+1    Segs0 = T#dict.segs,    Slot1 = N - T#dict.bso,    B = get_bucket_s(Segs0, Slot1),     Slot2 = N,<span style="color:#ff6666;">  %活跃slot数量增加了,对于hash值为n+1的slot2,之前存储到n+1-bso中的slot1中去了,所以现在需要重新计算</span>    [B1|B2] = rehash(B, Slot1, Slot2, T#dict.maxn),      Segs1 = put_bucket_s(Segs0, Slot1, B1),    Segs2 = put_bucket_s(Segs1, Slot2, B2),    T#dict{size=T#dict.size + Ic,    n=N,    exp_size=N * ?expand_load,  %从这里可以看到,    con_size=N * ?contract_load,    segs=Segs2};maybe_expand_aux(T, Ic) -> T#dict{size=T#dict.size + Ic}

1. 对于上面的代码有一个很大的疑问,按理说maxn变化了应对所有的元素重新做hash计算,但上面这里只对slot=n+1-bso重新计算,其它值呢?
        经过测试可以推测出,对于已经存在的hash值,只有cf对最小的哈希值计算会有影响,这跟erlang的哈希算法有关了
2.exp_size的大小为活跃slot数量的?expand_load=5倍,即平均每个bucket中的元素个数为5,当超过这个值时应扩展dict.
3.con_size的大小为活跃slot数量的?contract_load=3倍, #dict.size 小于这个值时,字典会进行压缩


从上面的代码中可以看到#dict.size >#dict.exp_size时,才需要对字典本身做一些调整,否则只是修改元素计数
函数maybe_expand_segs/0扩展segs的大小
maybe_expand_segs(T) when T#dict.n =:= T#dict.maxn ->    T#dict{maxn=2 * T#dict.maxn,    bso=2 * T#dict.bso,    segs=expand_segs(T#dict.segs, T#dict.empty)};maybe_expand_segs(T) -> T.

      可以看到当活跃slot达到最大slot数量时,翻倍扩展slot数量,expang_segs/2函数内容是报segs中seg的数量翻倍而已
0 0
原创粉丝点击