在 Go 中,map是一种内置的数据类型,用于表示哈希表。在其他编程语言中,你可能把 map 称作 dict,或者关联数组,或者键值存储。如果你还不熟悉这个概念,可以把 map想象成一本字典:里面的每个单词就是 key,而释义则是这个 map 的 element。
在开始之前,我想先向你推荐关于 map 的 Go 语言规范和关于 map 的 Go 博客,方便你更深入地了解
map。
从语法上看,map长这样:
map[KeyType]ElementType
KeyType必须是任何可比较类型,而 ElementType可以是 Go 中任何合法的类型,这意味着你可以存放从基本类型的变量到切片在内的任何东西。
有一点要记住:Go 中的map是无序的。如果你试着遍历一个 map 并打印其中的元素,可能会惊讶地发现元素是以随机顺序打印出来的(不妨自己试试)。
同样重要的是,每个键都是唯一的,也就是说,对同一个键赋值两次,会覆盖该键对应的值。
map是引用类型,这意味着如果你到处传递它,Go 不会复制整个 map。Go 所做的只是复制这个 map 的指针,这使得把 map 传给函数或变量的开销很低。未初始化的 map 的值是 nil。
你可以像下面这样定义一个 map(我们也把它称作 nil map):
var foo map[string]int
要初始化一个 map,可以这样做:
// With map literal
foo := map[string]int{}
或者
// or with make function
foo := make(map[string]int)
nil map 与已初始化的 map 不同,向 nil map 写入会导致运行时错误
panic: assignment to entry in nil map因此,在使用 map 之前先初始化它很重要
下面是一些你可以对 map 执行的操作
// Add a value in a map with the `=` operator:
foo["bar"] = 42
// Here we update the element of `bar`
foo["bar"] = 73
// To retrieve a map value, you can use
baz := foo["bar"]
// To delete an item from a map, you can use
delete(foo, "bar")
如果你尝试获取 map 中某个不存在的键的值,它会返回该值类型的零值。这可能会让你感到困惑,尤其是当你的ElementType的默认值(例如 int 的 0)本身就是一个合法值时。要检查某个键是否存在于 map 中,可以使用
value, exists := foo["baz"]
// If the key "baz" does not exist,
// value: 0; exists: false
正如前面看到的,判断一个map是否已初始化很容易,直接把它和 nil 比较即可。但如果是其他 map 呢?由于map不是可比较类型,我们不能使用==运算符,不过 reflect 包有办法帮我们,它叫做 DeepEqual,用法如下
import "reflect"
// ....
equal := reflect.DeepEqual(map[string]int{}, map[string]int{})
fmt.Println(equal)
// Output: true
不过等一下,既然 map 不是可比较类型,为什么我们还能把它和 nil 比较呢?其实规范为此开了个特例,参见可比较类型规范。
最后一点,如果你试图从多个 goroutine 向一个map写入,会触发竞态检测器,参见 Go 文档 和 Go 博客。你也可以改用 sync.Map、atomic 或 mutex 来绕过这个问题。