erlang的dict源码解析(1)
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)相关推荐
- erlang的dict源码解析(2)
dict:filter()可以通过传入一个断言作为参数,来对目标dict达到筛选的目的. filter(F, D) -> filter_dict(F, D).filter_dict(F, #di ...
- erlang的gb_trees源码解析
Erlang的gb_trees给出了平衡二叉树的实现. empty()函数给出了一个得到一个空的二叉树的途径. empty() ->{0, nil}. 空树作为一个含有两个成员的元组,第一个0则 ...
- erlang下lists模块sort(排序)方法源码解析(二)
上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...
- Redis源码解析——字典基本操作
有了<Redis源码解析--字典结构>的基础,我们便可以对dict的实现进行展开分析.(转载请指明出于breaksoftware的csdn博客) 创建字典 一般字典创建时,都是没有数据的, ...
- Redis源码解析——前言
今天开启Redis源码的阅读之旅.对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事.但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它.除了一些高大上 ...
- VVeboTableView 源码解析
原文链接:http://www.jianshu.com/p/78027a3a2c41 最近在看一些 iOS 性能优化的文章,我找到了 VVeboTableView 这个框架.严格来说这个不属于框架,而 ...
- python flask源码解析_用尽洪荒之力学习Flask源码
[TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...
- Flask werkzeug 源码解析
Flask werkzeug流程大概:执行run_simple ,实际执行为先用make_server 创建一个 BaseServer 实例,然后执行 实例的serve_forever 方法, ser ...
- [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构
[源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 文章目录 [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 0x00 摘要 0x01使用 1.1 ...
最新文章
- 配置SQL Server 2008 镜像
- Boost::context模块callcc的回声测试程序
- 使用Nodejs实现的小说爬虫
- 有哪些工具可以让嵌入式开发事半功倍?详细盘点工程师必备工具
- 2021牛客暑期多校训练营4 B - Sample Game 期望dp\生成函数
- discuz安装_手动搭建 Discuz! 论坛
- 前端学习(2668):删除功能
- 字符串类中的StringBuffer,StringBuilder
- JavaScript生成指定范围内的随机数
- java并发包作者lee_Java的一些并发包
- 简单python脚本实例-python下10个简单实例代码
- ASP.NET MVC 重点教程一周年版 第三回 Controller与View
- live2d web笔记之一:官方SDK尝试
- 20210211 plecs diode rectifier 二极管整流电路 zero crossing 报错
- 【苹果鼠标滑轮失灵】解决办法
- 刚刚 Kubernetes 1.25 正式发布,所有变化都在这儿了
- 普通用户强制修改root密码
- 使用Android Studio将开源库发布到Jcenter中央库
- JavaScript Date getTime() 方法
- 拯救频繁跳槽的必杀技!
热门文章
- Intellij IDEA自定义类注释模板
- 学习一门编程语言的基本步骤
- android电池剩余使用时间,android电池剩余使用时间
- java同步方法必须是静态的吗_Java基础知识之synchronized同步方法、代码块、静态方法、静态代码块的区别...
- c++11 string转ing_pdfkit | 利用python实现html文件转pdf
- ssh oracle id native,hibernate解决oracle的id自增?
- 雷军宣布:启动小米成立以来最大组织架构变革(附内部邮件原文)
- hausaufgabe--python 20- usage of Closure
- Hibernate 简介
- linux命令-tar命令