Erlang的dict模块功能类似于java的hashmap。

通过dict:new()构建新的dict。

new() ->Empty = mk_seg(?seg_size),#dict{empty=Empty,segs={Empty}}.mk_seg(16) -> {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}.

mk_seg()函数只给出了参数为16的实现,直接构造了一个包含了16个空列表的元组。这个元组被称为seg,其中的列表被称为slot。

而另一方面,dict中给出了dict的结构体。

-record(dict,{size=0         :: non_neg_integer(),    % Number of elementsn=?seg_size       :: non_neg_integer(),    % Number of active slotsmaxn=?seg_size        :: non_neg_integer(),  % Maximum slotsbso=?seg_size div 2  :: non_neg_integer(),    % Buddy slot offsetexp_size=?exp_size   :: non_neg_integer(),    % Size to expand atcon_size=?con_size   :: non_neg_integer(),    % Size to contract atempty        :: tuple(),   % Empty segmentsegs         :: segs(_, _)         % Segments}).

其中seg_size作为宏,被定义为了16。

结合dict的构造体与建立过程,一个dict构造体中segs作为存储空间,实则为一个元组,元组中包含若干元组作为seg,每个元组中包含16个列表,作为存储空间。

建立dict的过程,实则就是创建了一个包含16个列表的元组。

而dict:append()函数,则是用来向dict中添加相应的键值对。


append(Key, Val, D0) ->Slot = get_slot(D0, Key),{D1,Ic} = on_bucket(fun (B0) -> append_bkt(Key, Val, B0) end,D0, Slot),maybe_expand(D1, Ic).

在append()函数中,前两个参数为键值对,第三个则是要加入的dict,首先通过get_slot获得相应的哈希位置。

get_slot(T, Key) ->H = erlang:phash(Key, T#dict.maxn),ifH > T#dict.n -> H - T#dict.bso;true -> Hend.

在get_slot()中,首先根据key得到小于当前dict最大slot量的一个随机哈希值,如果得到的值大于当前可用的slot量,也就是n,则减去偏移量bso定位到可用的位置上。

在得到了哈希值,也就是Slot后,调用on_bucket()准备定位。

on_bucket(F, T, Slot) ->SegI = ((Slot-1) div ?seg_size) + 1,BktI = ((Slot-1) rem ?seg_size) + 1,Segs = T#dict.segs,Seg = element(SegI, Segs),B0 = element(BktI, Seg),{B1,Res} = F(B0),     %Op on the bucket.{T#dict{segs=setelement(SegI, Segs, setelement(BktI, Seg, B1))},Res}.

定位过程中,先除以16得到位于哪一个seg当中,之后在与16取余得到seg中所在slot位置。

之后调用作为参数传入的函数append_bkt()正式放入键值对。

append_bkt(Key, Val, [?kv(Key,Bag)|Bkt]) -> {[?kv(Key,Bag ++ [Val])|Bkt],0};
append_bkt(Key, Val, [Other|Bkt0]) ->{Bkt1,Ic} = append_bkt(Key, Val, Bkt0),{[Other|Bkt1],Ic};
append_bkt(Key, Val, []) -> {[?kv(Key,[Val])],1}.

如果得到已经是传入的slot本来就是一个空的列表,那么就直接加入键值对,同时返回1,说明dict中新增了一对键值对。

如果此时这个slot已经包含了键值对,则从头开始寻找slot中的列表一次查询是否存在key一样的,如果存在,则说明只需要更新当前的映射就行,此时也不是键值对了,而是key对应多个value的关系,类似于[key, value1, value2],由于只是更新,并没有添加新的元素,则返回0。而如果一直没有找到,则在这个slot的最后添加一个新的列表,作为新的键值对,并返回1。

在添加完毕之后,回到on_bucket()方法,更新dict中所定位到的那个slot得到新的dict,并一并返回之前的0或者1.

回到append()函数,调用maybe_expand()函数,将新得到的dict与0或者1作为参数传入。

maybe_expand(T, 0) -> maybe_expand_aux(T, 0);
maybe_expand(T, 1) -> maybe_expand_aux(T, 1).maybe_expand_aux(T0, Ic) when T0#dict.size + Ic > T0#dict.exp_size ->T = maybe_expand_segs(T0),   %Do we need more segments.N = T#dict.n + 1,     %Next slot to expand intoSegs0 = T#dict.segs,Slot1 = N - T#dict.bso,B = get_bucket_s(Segs0, Slot1),Slot2 = N,[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}.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.

maybe_expand()的调用实则是maybe_expand_aux()函数的调用。如果此时dict元素的个数size加上之前返回的1,大于最大数量,也就是exp_size也就是80那么将会进行扩容。如果小于或者加的是0,则会更新size的大小之后直接返回。

当需要扩容的时候,首先通过maybe_expand_segs()函数判断是否需要增加新的segs,其中dict的n表示可用的slot数量,如果与maxn相等,则需要扩容segs,那么获得新的dict,更新最大值maxn为两倍,并且同时扩张偏移量bso,通过expand-segs()扩张segs。

expand_segs({B1}, Empty) ->{B1,Empty};
expand_segs({B1,B2}, Empty) ->{B1,B2,Empty,Empty};
expand_segs({B1,B2,B3,B4}, Empty) ->{B1,B2,B3,B4,Empty,Empty,Empty,Empty};
expand_segs({B1,B2,B3,B4,B5,B6,B7,B8}, Empty) ->{B1,B2,B3,B4,B5,B6,B7,B8,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty};
expand_segs({B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16}, Empty) ->{B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty};
expand_segs(Segs, Empty) ->list_to_tuple(tuple_to_list(Segs)++ lists:duplicate(tuple_size(Segs), Empty)).

很简单,通过直接翻倍扩张。

在完成segs的扩容后,此时的n,也就是可用slot数量还没有更新。

回到maybe_expand_aux()中。

maybe_expand_aux(T0, Ic) when T0#dict.size + Ic > T0#dict.exp_size ->T = maybe_expand_segs(T0),   %Do we need more segments.N = T#dict.n + 1,     %Next slot to expand intoSegs0 = T#dict.segs,Slot1 = N - T#dict.bso,B = get_bucket_s(Segs0, Slot1),Slot2 = N,[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};

在完成segs的扩容后,活跃的slot + 1作为N,以n为16为例子,此时的N为17,Slot1为N减去扩张后的偏移量bso16结果为1,,通过get_bucket_s()函数得到相应的slot。

get_bucket_s(Segs, Slot) ->SegI = ((Slot-1) div ?seg_size) + 1,BktI = ((Slot-1) rem ?seg_size) + 1,element(BktI, element(SegI, Segs)).

这里得到相应的slot,结合上述数据,得到的是第一个seg中的第一个slot。

Slot2大小为17,之后通过1和17作为参数,通过rehash()函数重新分配刚才的slot中的数据。

rehash([?kv(Key,_Bag)=KeyBag|T], Slot1, Slot2, MaxN) ->[L1|L2] = rehash(T, Slot1, Slot2, MaxN),case erlang:phash(Key, MaxN) ofSlot1 -> [[KeyBag|L1]|L2];Slot2 -> [L1|[KeyBag|L2]]end;
rehash([], _Slot1, _Slot2, _MaxN) -> [[]|[]].

在rehash()中,依次根据新的maxn最大slot量重新计算slot中的key的hash,将分别命中1和17两个slot量的hash,放在前后两个列表中返回。

首先,选择返回得到的列表的前一个列表,通过put_bucket_s()函数更新原来位置的slot。

put_bucket_s(Segs, Slot, Bkt) ->SegI = ((Slot-1) div ?seg_size) + 1,BktI = ((Slot-1) rem ?seg_size) + 1,Seg = setelement(BktI, element(SegI, Segs), Bkt),setelement(SegI, Segs, Seg).

而后半部分则通过put_bucket_s()函数更新到新的segs上的对应的slot上。

之后更新元素个数,也就是加一,更新可用slot量,也是加一,得到扩容后的dict。

添加宣告完成。

erlang的dict源码解析(1)相关推荐

  1. erlang的dict源码解析(2)

    dict:filter()可以通过传入一个断言作为参数,来对目标dict达到筛选的目的. filter(F, D) -> filter_dict(F, D).filter_dict(F, #di ...

  2. erlang的gb_trees源码解析

    Erlang的gb_trees给出了平衡二叉树的实现. empty()函数给出了一个得到一个空的二叉树的途径. empty() ->{0, nil}. 空树作为一个含有两个成员的元组,第一个0则 ...

  3. erlang下lists模块sort(排序)方法源码解析(二)

    上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...

  4. Redis源码解析——字典基本操作

    有了<Redis源码解析--字典结构>的基础,我们便可以对dict的实现进行展开分析.(转载请指明出于breaksoftware的csdn博客) 创建字典 一般字典创建时,都是没有数据的, ...

  5. Redis源码解析——前言

    今天开启Redis源码的阅读之旅.对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事.但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它.除了一些高大上 ...

  6. VVeboTableView 源码解析

    原文链接:http://www.jianshu.com/p/78027a3a2c41 最近在看一些 iOS 性能优化的文章,我找到了 VVeboTableView 这个框架.严格来说这个不属于框架,而 ...

  7. python flask源码解析_用尽洪荒之力学习Flask源码

    [TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...

  8. Flask werkzeug 源码解析

    Flask werkzeug流程大概:执行run_simple ,实际执行为先用make_server 创建一个 BaseServer 实例,然后执行 实例的serve_forever 方法, ser ...

  9. [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构

    [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 文章目录 [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 0x00 摘要 0x01使用 1.1 ...

最新文章

  1. 配置SQL Server 2008 镜像
  2. Boost::context模块callcc的回声测试程序
  3. 使用Nodejs实现的小说爬虫
  4. 有哪些工具可以让嵌入式开发事半功倍?详细盘点工程师必备工具
  5. 2021牛客暑期多校训练营4 B - Sample Game 期望dp\生成函数
  6. discuz安装_手动搭建 Discuz! 论坛
  7. 前端学习(2668):删除功能
  8. 字符串类中的StringBuffer,StringBuilder
  9. JavaScript生成指定范围内的随机数
  10. java并发包作者lee_Java的一些并发包
  11. 简单python脚本实例-python下10个简单实例代码
  12. ASP.NET MVC 重点教程一周年版 第三回 Controller与View
  13. live2d web笔记之一:官方SDK尝试
  14. 20210211 plecs diode rectifier 二极管整流电路 zero crossing 报错
  15. 【苹果鼠标滑轮失灵】解决办法
  16. 刚刚 Kubernetes 1.25 正式发布,所有变化都在这儿了
  17. 普通用户强制修改root密码
  18. 使用Android Studio将开源库发布到Jcenter中央库
  19. JavaScript Date getTime() 方法
  20. 拯救频繁跳槽的必杀技!

热门文章

  1. Intellij IDEA自定义类注释模板
  2. 学习一门编程语言的基本步骤
  3. android电池剩余使用时间,android电池剩余使用时间
  4. java同步方法必须是静态的吗_Java基础知识之synchronized同步方法、代码块、静态方法、静态代码块的区别...
  5. c++11 string转ing_pdfkit | 利用python实现html文件转pdf
  6. ssh oracle id native,hibernate解决oracle的id自增?
  7. 雷军宣布:启动小米成立以来最大组织架构变革(附内部邮件原文)
  8. hausaufgabe--python 20- usage of Closure
  9. Hibernate 简介
  10. linux命令-tar命令