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 }