go map 需要注意的细节

Posted historyofsmile

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go map 需要注意的细节相关的知识,希望对你有一定的参考价值。

1.并发

map并发不安全

map并发不安全;并发地写入情况会报错:fatal error,情况有两种

  1. 几个goroutine同时写同一个map(没有读操作):fatal error: concurrent map writes
  2. 几个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

  1. 配合锁来使用——Mutex 、RwMutex
  2. 使用sync.Map
    1. 注意: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优缺点

优点

  1. 天然支持并发,并发使用没有心理障碍
  2. 读写分离的实现方式,使得它在读多写少的场景中性能比锁优秀

缺点

  1. 使用起来没那么方便,value类型是个interface,需要断言使用
  2. 读写分离的实现方式,读多写少的场景性能优越,但是写多的场景中,读操作命中率低,需要不断同步读写map,性能反而降低。

2. map初始化

map使用的时候要初始化,初始化的方式有两种:

m:=make(map[int]int)

m:=map[int]int

这两种初始化的方式都都可以正常使用。
如果只是定义了map而没有初始化,map 就是 nil,nil的map会发生什么呢?

  1. 读操作:是正常的——包括:获取某一个值、遍历,返回的是value的默认值
  2. 写操作:报 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必须支持相等运算符(==、!=)。

  1. go里面不支持这个运算符的数据类型:func、slice、map,所以不能用这三种类型作为map的key
  2. 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:

  1. 重新给key赋值一个新的结构体
  2. 用结构体指针做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 需要注意的细节的主要内容,如果未能解决你的问题,请参考以下文章

go map 需要注意的细节

go map 需要注意的细节

[Go] 开发 go web 项目,踩到的一些“坑”

Go管道注意细节

go语言中数组使用的注意事项和细节

Go语言容器—Map