golang map的实现源码在文件 runtime/map.go中,map的底层数据结构是hash表。
hash函数:经过指定的函数,将输入值从新生成获得一个散列值
hash表:散列值会肯定其键应该映射到哪个桶。而一个好的哈希函数,应当尽可能少的出现哈希冲突,以此保证操做哈希表的时间复杂度golang
接下来从下面三个方面讲解:算法
1. map的数据结构定义数组
type hmap struct { count int //map存储数据的个数,len(map)使用 flags uint8 //flags会标识当前map,好比hashWriting=4第4位表示有goroutine正在往map写入 B uint8 // map有 2^B 个buckets hash0 uint32 // hash算法的seed buckets unsafe.Pointer // 2^B 个Buckets的数组 oldbuckets unsafe.Pointer // 正在扩容期间,oldbuckets中有值,map是增量扩容,不是一次性完成。扩容主要是插入和删除操做会触发 ...... }
map数据结构图
map bucket的数据结构
一个bmap最多存储8个key/value对, 若是多于8个,那么会申请一个新的bucket,并将它与以前的bucket链起来。
tophash数组存储的key hash算法以后的高8位值安全
type bmap struct { // bucketCnt = 8 tophash [bucketCnt]uint8 }
对于map的操做,主要用到的是 查找,插入
新建map
新建map命令:
以a := make(map[string]string, len) 为例数据结构
源码在runtime/map.go文件里函数
func makemap(t *maptype, hint int, h *hmap) *hmap { //初始化hmap结构体 if h == nil { h = new(hmap) } h.hash0 = fastrand() //hint > 8, B一直增加到2的倍速 B := uint8(0) for overLoadFactor(hint, B) { B++ } h.B = B if h.B != 0 { //新建B个buckets,有可能须要新建overflow bucket h.buckets, nextOverflow = makeBucketArray(t, h.B, nil) if nextOverflow != nil { h.extra = new(mapextra) h.extra.nextOverflow = nextOverflow } } return h }
查找流程oop
a := map[string]string{"aa":"1", "bb": "2"}
以查找map: a中的key :"aa"为例ui
查找源码spa
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer { //若是有goroutine正在写入map,则不容许读。因此map是非线程安全的 if h.flags&hashWriting != 0 { throw("concurrent map read and map write") } ...... //肯定key所在的bucket内存地址 m := bucketMask(h.B) b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize))) //若是oldbuckets里面有值,从oldbuckets取值 if c := h.oldbuckets; c != nil { oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize))) if !evacuated(oldb) { b = oldb } } //获取key hash算法以后的高8位 top := tophash(hash) bucketloop: for ; b != nil; b = b.overflow(t) { //bucketCnt = 8,遍历tophash数组,tophash[i]等于kHash高8位的i值 而且 bucket的第i个的key与所给的key相等的value值 for i := uintptr(0); i < bucketCnt; i++ { if b.tophash[i] != top { continue } //对比 bucket的第i个的key与所给的key是否相等,相等就取对应的value值 k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize)) if alg.equal(key, k) { v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize)) return v } } } return unsafe.Pointer(&zeroVal[0]) }
插入流程
返回写入value值的内存地址
插入流程跟查找相似,线程
map插入的源码mapassign,返回写入value值的地址
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer { ..... again: //根据hash值,肯定bucket bucket := hash & bucketMask(h.B) if h.growing() { //是否正在扩容,若正在扩容中则先迁移再接着处理 growWork(t, h, bucket) } b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize))) top := tophash(hash) bucketloop: for { //遍历bucket的bitmap for i := uintptr(0); i < bucketCnt; i++ { if b.tophash[i] != top { //tophash 第i位没有赋值,而且是空槽,则记录下来,这是能够插入新key/value值的地址 if isEmpty(b.tophash[i]) && inserti == nil { inserti = &b.tophash[i] insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize)) val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize)) } if b.tophash[i] == emptyRest { break bucketloop } continue } //tophash第i位等于key hash值的高8位, k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize)) //key和k不匹配,继续遍历下一个k if !alg.equal(key, k) { continue } // map中已经存在key,value值 val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize)) goto done } //bucket不符合条件,开始遍历overbucket ovf := b.overflow(t) b = ovf } //map没有正在扩容 && 触发最大 LoadFactor && 有过多溢出桶 overflow buckets,则会触发扩容 if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) { hashGrow(t, h) goto again // Growing the table invalidates everything, so try again }