多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] ### **map 使用注意的点,是否并发安全?** map的类型是map\[key\],key类型的ke必须是可比较的,通常情况,会选择内建的基本类型,比如整数、字符串做key的类型。如果要使用struct作为key,要保证struct对象在逻辑上是不可变的。在Go语言中,map\[key\]函数返回结果可以是一个值,也可以是两个值。map是无序的,如果我们想要保证遍历map时元素有序,可以使用辅助的数据结构,例如orderedmap。 **第一,**一定要先初始化,否则panic **第二,**map类型是容易发生并发访问问题的。不注意就容易发生程序运行时并发读写导致的panic。 Go语言内建的map对象不是线程安全的,并发读写的时候运行时会有检查,遇到并发问题就会导致panic。 ### **map 循环是有序的还是无序的** 无序的,map 因扩张⽽重新哈希时,各键值项存储位置都可能会发生改变,顺序自然也没法保证 ### **怎么处理对 map 进行并发访问?有没有其他方案** 1、直接加锁,mutex ~~~ var mu sync.Mutex mu.Lock() //此处操作map mu.Unlock() ~~~ 2、sync.map ,底层也是加锁了 ~~~ // 并发安全的map var m = sync.Map{} func main() { wg := sync.WaitGroup{} // 对m执行20个并发的读写操作 for i := 0; i < 20; i++ { wg.Add(1) go func(n int) { key := strconv.Itoa(n) m.Store(key, n) // 存储key-value value, _ := m.Load(key) // 根据key取值 fmt.Printf("k=:%v,v:=%v\n", key, value) wg.Done() }(i) } wg.Wait() } ~~~ 3、RWMUTEX ,读写锁 ### **map 的数据结构是什么?是怎么实现扩容?** >**了解一下:** golang 中 map 是一个 kv 对集合。底层使用 hash table,用链表来解决冲突 ,出现冲突时,不是每一个 key 都申请一个结构通过链表串起来,而是以 bmap 为最小粒度挂载,一个 bmap 可以放 8 个 kv。在哈希函数的选择上,会在程序启动时,检测 cpu 是否支持 aes,如果支持,则使用 aes hash,否则使用 memhash ``` type hmap struct { count int // 元素的个数 B uint8 // buckets 数组的长度就是 2^B 个 overflow uint16 // 溢出桶的数量 ​ buckets unsafe.Pointer // 2^B个桶对应的数组指针 oldbuckets unsafe.Pointer // 发生扩容时,记录扩容前的buckets数组指针 ​ extra *mapextra //用于保存溢出桶的地址 } ``` **map 的容量大小** map 容量最多可容纳 6.5*2^B 个元素,6.5 为装载因子阈值常量。装载因子的计算公式是:装载因子=填入表中的元素个数/散列表的长度,装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。 **扩容条件1**:**装载因子 > 6.5**(源码中定义的) 即元素过多超过最大容量 正常情况下,如果没有溢出桶,那么一个桶中最多有8个元素,当平均每个桶中的数据超过了6.5个,那就意味着当前容量要不足了,发生扩容。 **扩容条件2**:**溢出桶的数量过多** 当 B < 15 时,如果overflow的bucket数量超过 2^B。 当 B >= 15 时,overflow的bucket数量超过 2^15。 ### **slices能作为map类型的key吗?** 不能 **不能作为map key 的类型包括:** * slices * maps * functions ### **Golang Map 如何扩容?** 双倍扩容:当前桶数组确实不够用了,**发生这种扩容时,元素会重排,可能会发生桶迁移**,扩容采取了一种称为“渐进式”的方式,原有的 key 并不会一 次性搬迁完毕,每次最多只会搬迁 2 个 bucket。 等量扩容:重新排列,极端情况下,重新排列也解决不了,map 存储就会蜕 变成链表,性能大大降低,此时哈希因子 hash0 的设置,可以降低此类极 端场景的发生。**元素会发生重排,但不会换桶** ### **Map 增删查到资料里**