声明:文中文字为原创+整理,图片来源于网络,只供自己复习,无任何侵权/盗取目的,如有请找我删除进行维权。
介绍:
map 是一种内置的数据类型,用于存储键值对(key-value pairs)。它提供了高效的键值对查找、插入和删除操作。map 可以看作是一种哈希表,键是唯一的,值可以是任意类型。其中,KeyType 是键的类型;ValueType 是值的类型,值可以是任意类型。
map[KeyType]ValueType
操作:
持插入、更新、访问、删除键值对,以及遍历 map。
创建map:
m := make(map[string]int)
插入:
m["banana"] = 7 // 插入新的键值对
m["apple"] = 6 // 更新已有键的值
键值类型:
- map 中的键是唯一的,插入相同的键会覆盖之前的值。
- map 的键类型必须是可比较的类型,能进行 == 和 != 运算;不允许使用切片(slice)、函数等不可比较的类型作为键。常见可作为键的类型包括:int、float、string、struct、pointer 等。
- 不可作为键的类型包括:slice、map 和 function,因为这些类型在 Go 中是不可比较的。
底层实现:
map 底层使用哈希表(hash table)实现,键通过哈希函数被映射到哈希表中的一个槽位。
哈希冲突采用开放寻址或链地址法来解决(Go 语言使用的是链地址法,将冲突的键存储在相同的桶中。)在链地址法中,每个桶bucket存储的是一个链表或数组,当多个键的哈希值相同时,这些键会被存储在同一个桶中。桶是一种存储一组键值对的结构,且 map 在负载因子超过一定阈值时,会触发自动扩容。
扩容:
- map 的扩容是通过重新分配更大的内存空间来完成的。
- map 在负载因子超过一定阈值时,会触发自动扩容。
- 每次扩容会使容量增加为原来的两倍,所有键值对会被重新哈希分配到新的桶中。扩容过程涉及大量的内存分配和数据迁移,性能开销较大。因此,在频繁插入或删除大量数据时,可能会影响程序的性能。
并发安全:
- Go 中的原生 map 不是并发安全的,在多线程环境中同时读写 map 会导致数据竞态。
- 使用 sync.Mutex 或 sync.RWMutex 来对 map 进行读写加锁,以确保线程安全。
- Go 1.9 之后引入了 sync.Map,它是一种并发安全的 map,适用于读多写少的场景。
垃圾回收:
- Go 中的 map 和其他变量一样受垃圾回收机制的管理。当键值对被删除后,键值对应的内存将被标记为可回收。
- 如果频繁插入和删除键值对,可能会导致碎片化和内存占用增大,建议定期将 map 重新分配(通过重新创建 map 并复制数据)。
性能优化:
- 使用 make 函数预分配 map 的容量,避免频繁扩容带来的性能开销。m := make(map[string]int)
选择合适的键类型,尽量使用简单的类型作为键,如 int、string,避免复杂结构。
在并发场景下,使用 sync.Map 或手动加锁来确保安全和高效的访问,确保 map 中的键顺序一致
零值与nil
- map 的零值是 nil,nil map 不能存储任何键值对,进行读取操作不会引发错误,写入操作会导致运行时错误。
- 空 map 是已分配内存但没有存储任何键值对的 map,可以正常进行读写操作。