golang map源码分析
2019独角兽企业重金招聘Python工程师标准>>>
1. map数据结构
Golang的map使用哈希表作为底层实现,一个哈希表里可以有多个哈希表节点,也即bucket,而每个bucket就保存了map中的一个或一组键值对。
map数据结构由runtime/map.go/hmap
定义:
type hmap struct {count int // 当前保存的元素个数...B uint8 // 指示bucket数组的大小...buckets unsafe.Pointer // bucket数组指针,数组的大小为2^B...
}
下图展示一个拥有4个bucket的map:
本例中, hmap.B=2
, 而hmap.buckets长度是2^B为4. 元素经过哈希运算后会落到某个bucket中进行存储。查找过程类似。
bucket
很多时候被翻译为桶,所谓的哈希桶
实际上就是bucket。
2. bucket数据结构
bucket数据结构由runtime/map.go/bmap
定义:
type bmap struct {tophash [8]uint8 //存储哈希值的高8位data byte[1] //key value数据:key/key/key/.../value/value/value...overflow *bmap //溢出bucket的地址
}
每个bucket可以存储8个键值对。
- tophash是个长度为8的数组,哈希值相同的键(准确的说是哈希值低位相同的键)存入当前bucket时会将哈希值的高位存储在该数组中,以方便后续匹配。
- data区存放的是key-value数据,存放顺序是key/key/key/...value/value/value,如此存放是为了节省字节对齐带来的空间浪费。
- overflow 指针指向的是下一个bucket,据此将所有冲突的键连接起来。
注意:上述中data和overflow并不是在结构体中显示定义的,而是直接通过指针运算进行访问的。
下图展示bucket存放8个key-value对:
3. 哈希冲突
当有两个或以上数量的键被哈希到了同一个bucket时,我们称这些键发生了冲突。Go使用链地址法来解决键冲突。
由于每个bucket可以存放8个键值对,所以同一个bucket存放超过8个键值对时就会再创建一个键值对,用类似链表的方式将bucket连接起来。
下图展示产生冲突后的map:
bucket数据结构指示下一个bucket的指针称为overflow bucket,意为当前bucket盛不下而溢出的部分。事实上哈希冲突并不是好事情,它降低了存取效率,好的哈希算法可以保证哈希值的随机性,但冲突过多也是要控制的,后面会再详细介绍。
4. 负载因子
负载因子用于衡量一个哈希表冲突情况,公式为:
负载因子 = 键数量/bucket数量
例如,对于一个bucket数量为4,包含4个键值对的哈希表来说,这个哈希表的负载因子为1.
哈希表需要将负载因子控制在合适的大小,超过其阀值需要进行rehash,也即键值对重新组织:
- 哈希因子过小,说明空间利用率低
- 哈希因子过大,说明冲突严重,存取效率低
每个哈希表的实现对负载因子容忍程度不同,比如Redis实现中负载因子大于1时就会触发rehash,而Go则在在负载因子达到6.5时才会触发rehash,因为Redis的每个bucket只能存1个键值对,而Go的bucket可能存8个键值对,所以Go可以容忍更高的负载因子。
5. 渐进式扩容
5.1 扩容的前提条件
为了保证访问效率,当新元素将要添加进map时,都会检查是否需要扩容,扩容实际上是以空间换时间的手段。
触发扩容的条件有二个:
- 负载因子 > 6.5时,也即平均每个bucket存储的键值对达到6.5个。
- overflow数量 > 2^15时,也即overflow数量超过32768时。
5.2 增量扩容
当负载因子过大时,就新建一个bucket,新的bucket长度是原来的2倍,然后旧bucket数据搬迁到新的bucket。
考虑到如果map存储了数以亿计的key-value,一次性搬迁将会造成比较大的延时,Go采用逐步搬迁策略,即每次访问map时都会触发一次搬迁,每次搬迁2个键值对。
下图展示了包含一个bucket满载的map(为了描述方便,图中bucket省略了value区域):
当前map存储了7个键值对,只有1个bucket。此地负载因子为7。再次插入数据时将会触发扩容操作,扩容之后再将新插入键写入新的bucket。
当第8个键值对插入时,将会触发扩容,扩容后示意图如下:
hmap数据结构中oldbuckets成员指身原bucket,而buckets指向了新申请的bucket。新的键值对被插入新的bucket中。 后续对map的访问操作会触发迁移,将oldbuckets中的键值对逐步的搬迁过来。当oldbuckets中的键值对全部搬迁完毕后,删除oldbuckets。
搬迁完成后的示意图如下:
数据搬迁过程中原bucket中的键值对将存在于新bucket的前面,新插入的键值对将存在于新bucket的后面。 实际搬迁过程中比较复杂,将在后续源码分析中详细介绍。
5.3 等量扩容
所谓等量扩容,实际上并不是扩大容量,buckets数量不变,重新做一遍类似增量扩容的搬迁动作,把松散的键值对重新排列一次,以使bucket的使用率更高,进而保证更快的存取。
在极端场景下,比如不断的增删,而键值对正好集中在一小部分的bucket,这样会造成overflow的bucket数量增多,但负载因子又不高,从而无法执行增量搬迁的情况,如下图所示:
上图可见,overflow的buckt中大部分是空的,访问效率会很差。此时进行一次等量扩容,即buckets数量不变,经过重新组织后overflow的bucket数量会减少,即节省了空间又会提高访问效率。
6. 查找过程
查找过程如下:
- 跟据key值算出哈希值
- 取哈希值低位与hmpa.B取模确定bucket位置
- 取哈希值高位在tophash数组中查询
- 如果tophash[i]中存储值也哈希值相等,则去找到该bucket中的key值进行比较
- 当前bucket没有找到,则继续从下个overflow的bucket中查找。
- 如果当前处于搬迁过程,则优先从oldbuckets查找
注:如果查找不到,也不会返回空值,而是返回相应类型的0值。
7. 插入过程
新员素插入过程如下:
- 跟据key值算出哈希值
- 取哈希值低位与hmap.B取模确定bucket位置
- 查找该key是否已经存在,如果存在则直接更新值
- 如果没找到将key,将key插入
转载于:https://my.oschina.net/tantexian/blog/3037976
golang map源码分析相关推荐
- Golang bytes源码分析
bytes/bytes.go源码分析 Golang JDK 1.10.3 bytes包提供了操作[]byte的常用方法. 源码分析 func equalPortable(a, b []byte) bo ...
- Golang map源码详解
Golang的map是用哈希表实现的,在实现性能上非常优秀,这里会主要对map创建.插入.查询.删除以及删除全部的源码做详解,刻意避开了扩容以及迭代相关的代码,后续会用一个新的文章去讲述.Golang ...
- go sync.map 源码分析
一 简言 1.1 文件所在目录:go/src/sync/map.go 1.2 本篇博客的go版本:go1.10.3 二 原理说明 空间换时间.通过冗余的两个数据结构(read.dirty),实现加锁对 ...
- Go语言——map 源码分析
之前自己整理的map源码浅析,还是有些不理解的地方,这篇转载曹大的笔记,借鉴下 原文地址:https://github.com/cch123/golang-notes/blob/master/map. ...
- GRPC golang版源码分析之客户端(一)
Table of Contents 1. 前言 2. 源码目录浏览 3. 客户端 4. 相关链接 1 前言 grpc是一个通用的rpc框架,用google实现,当然也有go语言的版本.在工作中主要用到 ...
- Golang map源码浅析
设计概述 runtime/map.go文件开头描述了map的设计要点. map是一个哈希表(hashtable): 数据被组织成bucket数组,每个bucket最多存8个键值对: 哈希值的低位用于选 ...
- Golang channel 源码分析
以下源码都摘自 golang 1.16.15 版本. 1. channel 底层结构 Golang 中的 channel 对应的底层结构为 hchan 结构体(channel的源码位置在Golang包 ...
- GRPC golang版源码分析之客户端(二)
Table of Contents 1. 前言 2. 负载均衡 3. 相关链接 1 前言 前面一篇文章分析了一个grpc call的大致调用流程,顺着源码走了一遍,但是grpc中有一些特性并没有进行分 ...
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
最新文章
- 使用动态代理简单模拟一下spring的事务管理
- 刘涵 美国 西北大学 计算机,西北大学关于表彰2010-2011学年度学生先进集体-红帆.doc...
- 远程服务器php环境搭建,免费网站搭建与phpstorm远程部署
- windows的php如何安装目录结构,禅道的目录结构
- 使用openssl生成双向加密证书(转)
- java包装项目_项目包装组织
- 前端学习(2491):refused to apply style from ‘‘ because its MIME type (‘text/html‘) is not a supported sty
- 领域应用 | 中医临床术语系统
- Java根据出生年月日获取到当前日期的年月日
- 腾讯、阿里、百度...大厂招聘火热中,测试员如何才能入大厂?
- SharePoint 2010问题集锦 (2011.1)
- 记我的一次重构——希望对新人有所帮助
- java quartz插件_JFinal Quartz 2.2.1插件
- Docker环境调优
- /etc/crontab文件和crontab -e命令区别
- 亚马逊封号,新规则来了,你知道了吗?
- 【Writeup】第六季极客大挑战(部分题目)
- 自己动手,丰衣足食------Rust实现自己的Stream
- 【一罐寡言】你的时间真的是不够用吗?
- 非暴力沟通--日常沟通的技巧与实践