go map源码分析(二)-插入元素

前面的文章介绍了map相关的数据结构以及map的创建流程(见:go map源码分析(一) - 创建map)

下面承接前面,通过具体的map操作实例,理解向map中插入元素的过程中都发生了什么,以更好的里面map以及使用map

map操作的代码

func main() {
    // map的创建,见前面的介绍
    mapV1 := make(map[string]string, 10) 

    // map中插入元素,下面的代码都是针对这句话进行的介绍
    mapV1["a"] = "a1"                                  
}

源码分析

func mapassign_faststr(t *maptype, h *hmap, s string) unsafe.Pointer {
    // h指向的就是一个map结构,就是前面的 mapV1
    if h == nil { // 如果map还没有申请内存,那么往map里面插入元素会发生panic
        panic(plainError("assignment to entry in nil map"))
    }
    if raceenabled {// 竞态检查
        callerpc := getcallerpc()
        racewritepc(unsafe.Pointer(h), callerpc, funcPC(mapassign_faststr))
    }
    if h.flags&hashWriting != 0 {
        throw("concurrent map writes")// 并发写发生
    }
    key := stringStructOf(&s)
    hash := t.key.alg.hash(noescape(unsafe.Pointer(&s)), uintptr(h.hash0)) // 计算hash值

    // Set hashWriting after calling alg.hash for consistency with mapassign.
    h.flags |= hashWriting // 这里设置了一个值,并发检查用 (如果并发写,会在前面抛出"concurrent map writes")

    if h.buckets == nil {// 如果map的buckets还没有分配,那么创建一个buckets
        h.buckets = newobject(t.bucket) // newarray(t.bucket, 1) // 在map创建的时候,有可能没有创建bucket,这里会进行创建
    }

again:
    bucket := hash & bucketMask(h.B) // 这里是计算出将key放入哪个bucket中,hash buckets个数为=2^B, hash mod 2^B = hash & (2^B-1)
                                        // 在创建map的时间,初始化的值写的为 10,因此创建map时,设置的 h.B=1, 具体见创建map
    if h.growing() { // 判断是否在增长期,增长期的map的size有可能会等于 or 大于 // 判断 方式:return h.oldbuckets != nil
        growWork_faststr(t, h, bucket)
    }
    // 这里直接通过指针的偏移计算出在那个桶里
    b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))
    top := tophash(hash) // 返回hash值的高8位

    var insertb *bmap
    var inserti uintptr
    var insertk unsafe.Pointer

    for {
        // 遍历一个桶
        for i := uintptr(0); i < bucketCnt; i++ { // 常量 bucketCnt = 8,每个bucket的空间为8
            // 如果top不相等,会遍历这个bucket中的下一个元素
            if b.tophash[i] != top { // 这里这样比较是有bmap的结构决定的,bmap的前面的开始是8个元素的数组,如果保存了值,说明对应的位置有元素
                if b.tophash[i] == empty && insertb == nil {
                    insertb = b
                    inserti = i
                }
                continue
            }
            // 走到这里的时候,说明top已经找到了
            // 关键步骤,这里这样计算是与bmap的结构紧密相关的,dataOffset=8,string占2*sys.PtrSize大小内存,通过指针的偏移计算出k
            k := (*stringStruct)(add(unsafe.Pointer(b), dataOffset+i*2*sys.PtrSize))
            if k.len != key.len {// 字符串的长度不相等,比较下一个元素
                continue
            }
            // 如果长度,但是key的值不相等,继续比较
            if k.str != key.str && !memequal(k.str, key.str, uintptr(key.len)) {
                continue
            }
            // already have a mapping for key. Update it.
            inserti = i
            insertb = b
            goto done
        }
        // 查看溢出bucket
        ovf := b.overflow(t)
        if ovf == nil {
            break
        }
        b = ovf
    }

    // Did not find mapping for key. Allocate new cell & add entry.

    // If we hit the max load factor or we have too many overflow buckets,
    // and we're not already in the middle of growing, start growing.
    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
    }

    if insertb == nil {
        // all current buckets are full, allocate a new one.
        insertb = h.newoverflow(t, b)
        inserti = 0 // not necessary, but avoids needlessly spilling inserti
    }
    // 插入元素
    insertb.tophash[inserti&(bucketCnt-1)] = top // mask inserti to avoid bounds checks
    // 通过指针偏移来找到bmap中正确位置
    insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*2*sys.PtrSize)
    // store new key at insert position
    *((*stringStruct)(insertk)) = *key
    h.count++

done:
    val := add(unsafe.Pointer(insertb), dataOffset+bucketCnt*2*sys.PtrSize+inserti*uintptr(t.valuesize))// 写值(如果原来有值,会覆盖掉原值)
    if h.flags&hashWriting == 0 {
        throw("concurrent map writes")
    }
    h.flags &^= hashWriting// 恢复标记位
    return val
}

总结

  • 向为nil的map中插入元素,会发生panic
  • 如果新加入的元素的key已经在map中了,那么这个key对应的value的旧值会被新值覆盖
  • map未加保护的并发写会发生fatal error: concurrent map writes,例如下面代码:
func main() {
    mapV1 := make(map[string]string, 10)

    go func() {
        for i := 0; i < 10000; i++ {
            mapV1["b"] = "b2"
        }
    }()
    for i := 0; i < 100000; i++ {
        mapV1["a"] = "a1"
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容