go map 需要注意的细节
Posted historyofsmile
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go map 需要注意的细节相关的知识,希望对你有一定的参考价值。
1.并发
map并发不安全
map并发不安全;并发地写入情况会报错:fatal error,情况有两种
- 几个goroutine同时写同一个map(没有读操作):
fatal error: concurrent map writes
; - 几个goroutine同时存在读和写操作:
fatal error: concurrent map read and map write
代码示例:https://gitee.com/historyofsmile/practice-interview/blob/master/%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0/map/%E5%B9%B6%E5%8F%91/%E4%B8%8D%E5%AE%89%E5%85%A8/main.go
注意:delete()也属于写操作
如何安全地使用并发地map
- 配合锁来使用——Mutex 、RwMutex
- 使用sync.Map
- 注意:sync.Map 并不是包治百病的大力丸
代码示例:https://gitee.com/historyofsmile/practice-interview/blob/master/%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0/map/%E5%B9%B6%E5%8F%91/%E5%AE%89%E5%85%A8/main.go
sync.Map优缺点
优点
- 天然支持并发,并发使用没有心理障碍
- 读写分离的实现方式,使得它在读多写少的场景中性能比锁优秀
缺点
- 使用起来没那么方便,value类型是个interface,需要断言使用
- 读写分离的实现方式,读多写少的场景性能优越,但是写多的场景中,读操作命中率低,需要不断同步读写map,性能反而降低。
2. map初始化
map使用的时候要初始化,初始化的方式有两种:
m:=make(map[int]int)
m:=map[int]int
这两种初始化的方式都都可以正常使用。
如果只是定义了map而没有初始化,map 就是 nil,nil的map会发生什么呢?
- 读操作:是正常的——包括:获取某一个值、遍历,返回的是value的默认值
- 写操作:报
panic: assignment to entry in nil map
总结:map并发 或 未初始化的情况中,会出现错误的都是写操作
3.map的扩容缩容
map底层实现是 hash散列表+链表 结构,map的容量是动态递增的,也就是说map没有缩容一说,要么容量保持不变,要么递增下去。
1.扩容:map占用的内存主要是由bucket的个数和overflow的数据量决定的,主要还是bucket的个数,当len(map)/B(log2(bucket个数))>=6.5时,map的bucket个数就会加倍,同时数据重新分配到新的bucket中;
2. 等量扩容:其实并没有扩容,只是做了数据重新分配。条件为溢出桶(noverflow)的数量 >= 32768(1<<15),此时许多bucket闲置,而部分bucket溢出,这个情况不太妙,就做等量扩容,数据重新分配。
3. delete() :就是把key对应的bucket位置置空,并不能释放这个位置的内存。
4. 可见,map本身就没有缩容的功能。如果map经过大量的删除操作后,数据量明显变少了,想实现缩容,只能手动创建一个新的map,把数据遍历再写进去。
map底层实现可参:https://mp.weixin.qq.com/s/2CDpE5wfoiNXm1agMAq4wA
4.map的key
map的key必须支持相等运算符(==、!=)。
- go里面不支持这个运算符的数据类型:func、slice、map,所以不能用这三种类型作为map的key
- math.NaN()作为map的key,因为NaN每次hash的结果都不一样,所以用NaN作为key保存的数据读取不出,并且每保存一次都会多一个NaN作为key的数据。
总结:func、slice、map 不能作为map的key, math.NaN()不要作为map的key
代码示例:https://gitee.com/historyofsmile/practice-interview/blob/master/%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0/map/key/main.go
5.结构体做map的value
map是不可寻址的(not addressable),如果用结构体作为map的value,不能直接修改value的字段值。
解决办法有2:
- 重新给key赋值一个新的结构体
- 用结构体指针做map的value
示例代码:https://gitee.com/historyofsmile/practice-interview/blob/master/%E5%9F%BA%E7%A1%80%E7%BB%83%E4%B9%A0/map/struct%E5%81%9Avalue/main.go
6.map本身就是指针
map本身就是个指针,用map做函数参数时,要注意函数内部同样会修改map底层的数据,也就是说实参和形参指向同一个实体,这一点要注意
以上是关于go map 需要注意的细节的主要内容,如果未能解决你的问题,请参考以下文章