go-源码研读-map
代码版本:1.12.7
主要代码在src/runtime/map.go
结构图
常量定义
每个bucket持有kv键值对数目1
2
3
4// Maximum number of key/value pairs a bucket can hold.
//设置一个bucket最多持有多kv键值对
bucketCntBits = 3
bucketCnt = 1 << bucketCntBits
扩容因子1
2
3
4
5 // Maximum average load of a bucket that triggers growth is 6.5.
// Represent as loadFactorNum/loadFactDen, to allow integer math.
//判断是否扩容的因子,按注释的意思是经多次测试选了6.5这个值,也就是当元素个数/bucket个数大于等于6.5时(换算下来就是元素个数80%左右的时候扩容),就会进行扩容,把bucket数量扩成原本的两倍
loadFactorNum = 13
loadFactorDen = 2
kv最大的大小,超过这个大小会转成指针来存储1
2
3
4
5
6
7 // Maximum key or value size to keep inline (instead of mallocing per element).
// Must fit in a uint8.
// Fast versions cannot handle big values - the cutoff size for
// fast versions in cmd/compile/internal/gc/walk.go must be at most this value.
//kv最大的大小,超过了这个大小就用指针来存储,单位是字节
maxKeySize = 128
maxValueSize = 128
bmap
的定义只有部分内容,剩下的需要通过偏移来查找1
2
3
4
5
6
7
8 // data offset should be the size of the bmap struct, but needs to be
// aligned correctly. For amd64p32 this means 64-bit alignment
// even though pointers are 32 bit.
//数据的偏移量,因为bmap的结构里面只定义了一个 tophash,剩下的kv和ovf都是通过偏移量来算出来的,这么做的意图是为了保证兼容32位机和64位机
dataOffset = unsafe.Offsetof(struct {
b bmap
v int64
}{}.v)
tophash
的状态1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// Possible tophash values. We reserve a few possibilities for special marks.
// Each bucket (including its overflow buckets, if any) will have either all or none of its
// entries in the evacuated* states (except during the evacuate() method, which only happens
// during map writes and thus no one else can observe the map during that time).
//记录每个bucket内坑位的状态
//标示所在单元是个空单元,后续也么有非空单元
emptyRest = 0 // this cell is empty, and there are no more non-empty cells at higher indexes or overflows.
//标示所在单元是个空单元
emptyOne = 1 // this cell is empty
//标示数据有效,数据迁移在新表的前半部分,同大小扩容位置一样
evacuatedX = 2 // key/value is valid. Entry has been evacuated to first half of larger table.
//标示数据有效,数据迁移在新表的后半部分
evacuatedY = 3 // same as above, but evacuated to second half of larger table.
//标示所在单元是个空单元,相关数据已经迁移到新的hmap中
evacuatedEmpty = 4 // cell is empty, bucket is evacuated.
//标示正常单元tophash状态的最小值,因为0-4是做状态判断的
minTopHash = 5 // minimum tophash for a normal filled cell.
迭代器标记1
2
3
4// flags
//两个迭代器
iterator = 1 // there may be an iterator using buckets
oldIterator = 2 // there may be an iterator using oldbuckets
写入状态标记,通过这个标记来判断是否同时读写1
2//写入状态
hashWriting = 4 // a goroutine is writing to the map
扩容标记,用来判断是等比扩容(rehash)还是二倍扩容1
2//标记是同大小扩容
sameSizeGrow = 8 // the current map growth is to a new map of the same size
迭代器使用的一个标记值,用来判别真实数据1
2
3//用来做迭代器哨兵的一个值
// sentinel bucket ID for iterator checks
noCheck = 1<<(8*sys.PtrSize) - 1
0值定义1
2const maxZero = 1024 // must match value in cmd/compile/internal/gc/walk.go
var zeroVal [maxZero]byte
类型定义
1 | // A header for a Go map. |
函数定义
工具函数
1 | // bucketShift returns 1<<b, optimized for code generation. |
核心函数
新建map a :=make(map[string]interface{})
新建步骤如下
- 如果hmap结构已经传入,复用,否则,即新创建一个。
- 根据元素数量,确定桶大小数量B(2^B个桶)
- 如果B=0,直接返回h即可,即不申请具体buckets内存,后续惰性申请。
- 申请内存块,这里的逻辑是如果B>=4, 即桶数量如果大于等于16时, 会增加1/16*B个桶,桶数量向上取整并使内存对齐, 这时候,根据对齐后的内存, 重新计算桶数量 。将对齐后的内存块,初始化为N个桶。 对于前2^B个桶, 都作为哈希表中的桶, 后面的桶作为哈希表的溢出桶。溢出桶是指:如果出现当前桶空间不足,将当前桶空间后面的ovf指针上挂上一个新的桶,来进行存储元素(类似链接法解决哈希冲突)。这个桶就叫溢出桶。
1 | // makemap implements Go map creation for make(map[k]v, hint). |
取值相关 a := m["a"] a,ok := m["a"] for k,v:=range m["a"]
map查找步骤如下:
- 根据哈希算法,以及哈希因子可以得到将key哈希后的值hashKey
- 将hashKey&(1<<B-1)即可知道当前的key所在的桶编号,根据桶编号,将指针偏移即可得到key所在桶的桶指针b。
- 判断当前hmap是否是处于扩容状态。 如果处于扩容状态,在旧hmap中在执行第二步,即获取在旧hmap中的桶指针oldb,根据桶指针,可以判断出当前桶是否已经迁移,未迁移,b=oldb
- 根据hash值,获取tophash值 ,即高8位字段,如果小于4 则+4。 在tophash值一致的情况下 获取key, 进行比对,如果相等,获取value 并返回。 不等, 继续循环。
1 | //几个名字,简单明了,1就是返回value,2就是value+bool,k就是返回key+value。实现方式也都一样 |
map赋值 m["a"]=1
1 | // Like mapaccess, but allocates a slot for the key if it is not present in the map. |
map删除
1 | func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) { |
扩容
数据搬迁不是一次性完成,而是逐步的完成(在insert和remove时进行搬移),这样就分摊了扩容的耗时。同时为了避免有个bucket一直访问不到导致扩容无法完成,还会进行一个顺序扩容,每次因为写操作搬迁对应bucket后,还会按顺序搬迁未搬迁的bucket,所以最差情况下n次写操作,就保证搬迁完大小为n的map。
1 | func growWork(t *maptype, h *hmap, bucket uintptr) { |