首先来看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,
%活跃slot数量增加了,对于hash值为n+1的slot2,之前存储到n+1-bso中的slot1中去了,所以现在需要重新计算
[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的数量翻倍而已