用过 C++ 的 STL 的人肯定见过 Map,或者说这东西相当于 Python 中的字典

简单地说,就是用来存储键值对的结构,键(key)必须是可比较类型,而值(value)可以是任意类型

创建 Map

var

1
var m map[string]int

使用 make

1
m = make(map[string]int)

使用字面值

1
m = map[string]int{"one": 1, "two": 2}

当然你可以指定内容为空

1
m = map[string]int{}

通过使用空接口,你可以在同一个 map 中存储不同类型的 value(就像 Python 中的字典一样)

1
2
3
4
m = map[string]interface{}{
"name": "张三",
"age": 18,
}

当然现在空接口有一个语法糖 any ,不够我还是习惯旧的写法

添加元素

直接使用索引添加

1
2
ages["alice"] = 31
ages["charlie"] = 34

访问 Map

直接使用索引访问

1
fmt.Println(ages["alice"]) // "32"

如果你尝试访问一个不存在的键,会返回零值

如果你愿意接收两个返回值,那么会返回结果和是否存在的 bool

所以你会经常看见这样的写法

1
if age, ok := ages["bob"]; !ok { /* ... */ }

另外一种是使用 for k,v:= range m 来迭代,分别取出其中的 key 和 value

但是需要注意的是,迭代出来的顺序是乱的

当然你可以只接收一部分,例如 for _,v:= range m

修改元素

  • 直接拿出来修改

    1
    ages["bob"] = ages["bob"] + 1
  • 如果你的值是个结构体等复杂结构,你不能修改结构体中的内部值,只能构造一个新的结构体然后重新给map赋值

    image-20221107101157461

  • 和切片一样,使用 for k,v:= range m 时,修改 v 是没有用的,你必须使用 m[k] 来修改

删除元素

使用内置的 delete 函数

1
delete(ages, "alice")

开发常见问题

比较两个 Map

和切片一样,Map 直接不能直接使用 == 来比较,除了和 nil 来比较

你只能手动遍历来比较

1
2
3
4
5
6
7
8
9
10
11
func equal(x, y map[string]int) bool {
if len(x) != len(y) { // map 也是可以使用 len() 的
return false
}
for k, xv := range x {
if yv, ok := y[k]; !ok || yv != xv {
return false
}
}
return true
}

Golang 中有没有 Set?

没有,你可以使用值为 bool 的 map 来模拟 set

一个小技巧

map 的 key 必须是可以比较的,所以你不能直接创建一个 key 是切片的 map

但是你可以通过一个辅助函数将其转换成可比较的类型,再封装一层

1
2
3
4
5
6
var m = make(map[string]int)

func k(list []string) string { return fmt.Sprintf("%q", list) }

func Add(list []string) { m[k(list)]++ }
func Count(list []string) int { return m[k(list)] }

使用同样的技术可以处理任何不可比较的 key 类型,而不仅仅是 slice 类型

这种技术对于想使用自定义 key 比较函数的时候也很有用,例如在比较字符串的时候忽略大小写

有没有清空的方法?

暂时无,请使用 range 然后一个一个删除

详情见 Go 大佬良心发现,愿意给 map 加清除了?