image.png


    map简介

    在Go语言中,一个map就是一个哈希表的引用。也就是说,map数据类型的底层实现是哈希表。所有map的很多特性要在理解底层哈希表的基础之上才能更好的理解。本文先总结go语言中map的基本使用方式,然后提出一些map相关的问题,这些问题在理解哈希表之后即可解决。


    Map基本使用

    1. package main
    2. import "fmt"
    3. func main() {
    4. elements := make(map[string]string)//make语创建map
    5. elements["h"] = "hello"//赋值
    6. emptyMap := map[string]float64{}//创建空map
    7. myMap := map[int]int{1: 1, 2: 2, 4: 3}//按字面量赋值
    8. var nilMap map[int]string
    9. //nilMap[3] = "three"
    10. //空的字典不能够直接添加元素
    11. nilMap = make(map[int]string)
    12. nilMap[3] = "three"
    13. delete(nilMap,3)//删除元素
    14. _=&elements["h"]//map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作
    15. fmt.Println(elements)
    16. fmt.Println(myMap[1])
    17. fmt.Println(myMap)
    18. fmt.Println(emptyMap)
    19. fmt.Println(nilMap)
    20. }

    Map默认遍历

    1. ages=make(map[string]int){
    2. "alice":31,
    3. "tom":26,
    4. }
    5. for name,age:=range ages{
    6. fmt.Printf("%s\t%d\n",name,age)
    7. }

    Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序


    Map顺序遍历

    如果需要按顺序遍历key/value对,我们必须显式地对key进行排序

    1. import "sort"
    2. var names []string
    3. for name := range ages {
    4. names = append(names, name)
    5. }
    6. sort.Strings(names)
    7. for _, name := range names {
    8. fmt.Printf("%s\t%d\n", name, ages[name])
    9. }

    Map nil值

    1. var ages map[string]int
    2. fmt.Println(ages == nil) // "true"
    3. fmt.Println(len(ages) == 0) // "true"
    4. ages["carol"] = 21 // panic: assignment to entry in nil map

    一定要牢记一点,map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常。

    这是因为map类型的零值是nil,也就是没有引用任何哈希表。

    在向空的map存数据前必须先创建map.

    1. ages=make(map[string]int)//使用make函数创建map,并对空字典ages重新赋值

    Map 索引

    通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在,那么将得到value对应类型的零值。
    那么,如何判断索引是否存在,可以使用ok值

    1. age, ok := ages["bob"]//存在时,ok==true
    2. if !ok { /*键不存在时的执行逻辑*/ }

    关于Map,有下面几个问题:

    1. 字典的键类型不能是哪些类型
    2. 应该优先考虑哪些类型作为字典的键类型
    3. 字典类型的值是并发安全的吗?

    第一问

    1.对于第一个问题,标准答案是,字典的键不能是不支持 ==和!=运算符的类型,例如,slice,函数,字典类型。

    Go 语言的字典类型其实是一个哈希表(hash table)的特定实现,在这个实现中,键和元素的最大不同在于,键的类型是受限的,而元素却可以是任意类型的。

    而go语言哈希表的大致实现过程是:

    1. 1. 计算键的哈希值
    2. 1. 一个哈希表会持有一定数量的桶(bucket
    3. 1. 根据哈希值在哈表中找到对应的桶(bucket
    4. 1. 此时,在哈希桶中,用键值本身去逐个对比元素

    只所以,最终还要使用键值本身进行对比,是因为不同值的哈希值是可能相同的。这有个术语,叫做“哈希碰撞”。

    综上所述,键的值一定要可以支持==运算才可以。

    go字典底层实现的具体细节

    第二问:

    • 求哈希和判等操作的速度越快,对应的类型就越适合作为键类型

    • 不建议用字典、结构体等高级数据类型作为键

    • 优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。

    第三问:

    非原子操作需要加锁, map并发读写需要加锁,map操作不是并发安全的,判断一个操作是否是原子的可以使用 go run race 命令做数据的竞争检测
    (在并发章节在举例说一下)