和大部分编程语言一样,Golang也提供了一种数据类型用来存储键值对(hash)数据,这篇文章我们一起来探讨Golang中map的使用方法以及常见的注意事项
map的底层结构我们通过以下代码来看下,当我们创建一个map,并往map中添加键值对时,底层都做了些啥
func main() {
// 声明一个map m1
m1 := make(map[string]int)
// 添加键值对 a:1
m1["a"] =1
}
map会初始时,内部有一个hash表,该表中有一定数量的hash桶。当我们往hash表中添加键值对时,map会先用hash函数计算key的hash值,然后根据hash值定位到具体的hash桶。
map中的查找逻辑也是类似的
不能做key的数据类型
我们通过Go语言规范中可以知道,map中的key必须是可以进行判等的(== 或 !=),而「函数类型」、「字典类型」、「切片类型」在Golang设计之初就是不支持判等的(我猜是因为这三者都属于引用类型,对指针指向的元数据是否相等不太好判定)。所以map中的键不能是「函数类型」、「字典类型」、「切片类型」
a1 := []int{1, 2}
a2 := a1
// 这行代码会出现编译错误
// Invalid operation: a1 == a2 (the operator == is not defined on []int)
fmt.Println(a1 == a2)
最好不要做key的数据类型
Golang中是可以用interface作为key的,但是一般不建议这么玩!究其原因我们会在接下来的文章中详细分析
func main() {
m1 := map[interface{}]string{
"a": "aa",
[]string{"1"}: "bb", // 这行代码会触发panic runtime error: hash of unhashable type []string
}
fmt.Println(m1)
}
我们声明并初始化key为interface类型的map(可以正常编译通过),当key的动态类型为值类型是没有问题的。但是当key的动态类型为不可比较的类型时(比如切片),此时程序运行时会panic。所以Go规范中一般不建议将key设置为interface类型,因为会躲过程序的编译器检查,导致程序不可控。
为什么不能将不可比较的类型当做key文章的开始我们已经描述了map的底层结构,当我们进行hash查找时,首先会用hash函数计算key的哈希值,根据hash值找到对应的bucket。最后再拿原始key值和bucket中的key值进行比较。这里有人就会问了,直接比较hash值不就可以了么,为什么还需要比较原始key值?
因为不同key值可能会产生相同的hash值(哈希冲突),所以只有当hash值相同并且原始key相同才证明是我们要查找的元素。讲到这里我想你应该明白为什么Golang中要求key的类型要求为可比较类型了吧
不要对值为nil的map进行添加键值对操作func main() {
var m1 map[int]string
// 这行代码会触发panic
// m1[1] = "aa"
fmt.Println(m1[2])
delete(m1, 2)
}
如上代码所示,我们不要对值为nil的map进行添加/修改键值对操作,这里会造成 panic: assignment to entry in nil map,如果你有用编辑器的话(例如Goland),编辑器会给出对应的告警。但是我们对值为nil的map进行查询和删除时,程序是能够正常运行的。不过我还是建议你使用make来声明map