Go入门指南(map) | 豆包MarsCodeAI刷题

139 阅读3分钟

Map

Go语言实现的map是一种无序的基于键值对(key-value)的数据结构,为引用类型,使用前必须初始化

声明

map类型的变量的初始值默认为nil,直接使用会导致panic
需使用make进行内存分配,完成初始化,也可以直接使用:=完成

func main() {
    var mp1 map[int]int
    mp1[1] = 1          // panic 只是声明了 但没有分配空间
    fmt.Println(mp1[1]) //panic: assignment to entry in nil map

    mp2 := map[int]int{}
    mp2[1] = 1
    fmt.Println(mp2[1])  // 1
    
    mp3 := make(map[int]int)
    mp3[1] = 1
    fmt.Println(mp3[1]) // 1
}

使用

直接对map[key]进行操作,若key不存在会自动创建,存在则会覆盖value(简单类型)

func main() {
    mp := make(map[string]string)
    mp["test"] = "hello world"
    fmt.Println(mp["test"]) // hello world

}

判断key是否存在

map[key]有两个返回值
val, ok := map[key] val为值,ok是bool类型变量, true则表示此key存在于这个map中,当不存在时,val为对应类型的零值 (string 为 "", int,float32 64为 0 , bool 为false)

func main() {
    mp := make(map[string]bool)
    mp["test"] = true
    if val, ok := mp["test"]; ok {
       fmt.Println(val) //true
    }
    val, ok := mp["test1"]
    fmt.Println(val, ok) //false false
}

map的遍历

使用for k, v := range map 语法,其中k为键(key), v为值(value), 不需要某个值时使用_忽略

注意: map是无序的,所以你存储的顺序和输出顺序很大可能是不一致的

func main() {
    mp := map[string]int{
        "A":100,
        "B":90,
        "C":80,
    }
    for _,v := range mp{
        fmt.Println(v) //三个数随机输出 因为map是无序的
    }
}

要想得到有序结果,只能将值取出来放入切片等结构进行排序得到顺序值
对于不需要的键我们可以使用delete函数进行删除

func main() {

    mp := map[string]int{"a": 1, "b": 2, "c": 3}
    v, ok := mp["a"]
    fmt.Println(v, ok) // 1 true
    delete(mp, "a")
    v, ok = mp["a"]
    fmt.Println(v, ok) // 0 false

}

我们也可以利用map来实现很多的非内置的数据结构,如

使用map实现set(集合 每个元素只能存在一次),利用struct{}空结构体当作值

func main() {

    mp := make(map[int]struct{})
    nums := []int{1, 5, 3, 6, 7, 3, 2, 7, 4}
    for _, num := range nums {
       mp[num] = struct{}{}
    }
    fmt.Println(len(mp)) // 7
}

值也可以为复杂类型,如切片

func main() {

    mp := make(map[int][]int)
    nums := []int{1, 5, 3, 6, 7, 3, 2, 7, 4}
    for i, num := range nums {
       mp[num] = append(mp[num], i)
    }
    fmt.Println(mp[3]) // 2 5
}

此时作用就是记录了每个数字对应的一个或多个相同值的下标

map底层

map底层由哈希表实现,一个hash表中存在多个桶(bucket),每个桶中保存了map中的一个或一组键值对

type hmap struct{
    count int //当前保存的元素个数
    B         //桶的大小
    buckets unsafe.Pointer //桶数组 长度是 2 ^ B
    oldbuckets unsafe.Pointer //旧桶 用于扩容
}

官方给出的每个bucket可以存储8个键值对
hash冲突是不可避免的,会降低我们的存储效率,Go的map使用链地址法来解决hash冲突,用类似链表的结构将桶串联起来,但其实最坏的情况会导致完全成为一条链
如果冲突过多就会触发rehash的操作(负载因子过高) 负载因子 = 键数量 / 桶数量

  • 负载因子过小,空间利用率低
  • 负载因子过大,冲突严重,存取效率低(链式查询需要O(n) hash的效率是O(1))

rehash就是解决负载因子过大的一种手段,他会对所有的键值对进行重排,使其均匀的分布在这些桶内

扩容也是降低负载因子的常用手段(空间换时间)

Go的扩容机制很巧妙 利用了旧桶去指向原本的桶位置,新桶会扩容到原本的2倍,采取逐步搬迁的策略,每次访问搬走两个桶,在搬迁过程中,查询会从旧桶开始查询,没有才会访问新桶,而添加则是直接在新桶进行操作