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"
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容