前面的文章介绍了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"
}
}