go map實現

golang map的實現源碼在文件 runtime/map.go中,map的底層數據結構是hash表。
hash函數:經過指定的函數,將輸入值從新生成獲得一個散列值
hash表:散列值會肯定其鍵應該映射到哪個桶。而一個好的哈希函數,應當儘可能少的出現哈希衝突,以此保證操做哈希表的時間複雜度golang

接下來從下面三個方面講解:算法

  • map數據結構
  • map查找實現
  • map插入實現

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數據結構圖
WechatIMG7896.jpeg
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) 爲例
image.png數據結構

  1. 初始化map的結構體hmap
  2. 計算hmap的buckets數量,用hmap.B記錄。若是len < 8,hmap.B = 0, 大於8,hmap.B等於len下一個2的指數倍。好比len =14,下一個2的倍速就是16,2^4 = 16,因此hmap.B = 4
  3. 新建 2 ^ hmap.B 個buckets

源碼在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

  1. 計算a散列函數以後的hash值kHash,假設爲 8E232230FFFFFFFF(16進制)
  2. 根據kHash低八位計算肯定 bucket 的內存地址, kHash的低8位爲FFFFFFFF。
  3. kHash的高8位是8E232230,遍歷第2步找到bucket的tophash數組,找到tophash[i]等於8E232230
  4. 找到bucket的第i個的key與所給的key:aa相等。若是相等,則返回其對應的value,反之則繼續進行第3步
  5. 遍歷完bucket沒找到,在overflow buckets中從第2步繼續尋找。

查找源碼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值的內存地址
插入流程跟查找相似,線程

  1. 置位flags hashWriting位爲1
  2. 根據key hash以後的值kHash
  3. 根據kHash低八位計算肯定 bucket 的內存地址
  4. 判斷是否正在擴容,若正在擴容中則先遷移再接着處理
  5. 遍歷bucket和overbucket的bmap,記錄第一個空槽,並把該位置標識爲可插入 tophash 位置,這裏就是第一個能夠插入數據的地方。若是找到匹配的key值k,則返回該k對應的value值地址
  6. 若是bucket已經full,則申請新的bucket做爲overbucket,插入key/value鍵值對。
  7. map沒有正在擴容 && 觸發最大 LoadFactor && 有過多溢出桶 overflow buckets,則會觸發擴容

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
    }
相關文章
相關標籤/搜索