Map的结构
//A header for a Go map.
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
核心的字段: B:Buckets数组的长度就是2^B
buckets:指向了bucket数组,大小为2^B
buckets指向的结构体
type bmap struct {
topbits [8]uint8 //高八位,用来快速定位
keys [8]keytype
values [8]valuetype
pad uintptr
overflow uintptr
}
bmap就是我们常说的桶,每个桶里面会装8个key,当超过8个key后,会新建bmap通过overflow链接。
我们看下bmap的图解
来一个整体的图
map的set过程
1).对key进行hash计算,得到的结果如图
10010111 | 000011110110110010001111001010100010010110010101010 │ 01010
2).计算落在哪个桶里
通过对hash值得后B位,如果B=5,那么通过上值得到10,因此我们放入第10个桶。
3).计算落在哪个桶里的哪个位置
如果第一个key是空,就放在这里,并且将高8位存入HobHash(存入后方便查询时迅速找到)。
hash冲突问题
在set时难免会遇到hash冲突,如果当前key已经有值,就放到下一个key。如果整个bucket满了,就放到overflow链接的下一个bucket。
map的get操作
10010111 | 000011110110110010001111001010100010010110010101010 │ 01010
1).寻找属于哪个桶 根据hash值的后B位找到位于哪个桶
2).寻找位于桶里具体位置 根据高8位直接定位到对应的HobHash,找到对应的key后进行比较,如果相等返回,不相等向下找寻,如果这个桶中找不到相应的key,则根据overflow去下一个桶中查找(正是因为key需要比较,所以 map的类型不可以是引用类型)
下图特别形象的展示了map的高位和低位如何定位桶数据
hash的扩容机制
map有两种扩容
当map中的元素越来越多,碰撞就会越来越多。增删改查效率会越来越低。 Go引入了装载因子:
loadFactor := count / (2^B)
count是元素的个数,(2^B)是桶的个数。
扩容机制: 1.装载因子超过6.5
也就是当map的元素个数大,而桶少,说明桶中元素过多,碰撞会非常激烈,因此需要进行扩容buckets,将B加1,相当于存放桶的list增长2倍。
2.当overflow的bucket过多,而实际上每个bucket装载的key又很少。我们需要申请新的bucket,将这些旧bucket存入这个新bucket,让key更加紧凑。
触发条件: 当B<15时,也就是 bucket 总数 2^B 小于 2^15 时:如果overflow的bucket数量超过2^B。
当B>=15时,也就是 bucket 总数 2^B 大于等于 2^15 时:如果overflow的bucket数量超过2^15。
扩容不是立即完成的,而是在每次修改和删除map的时候将旧的bucket转移到新的bucket中,每次转移两个。