对于哈希表(hash table) 这种数据结构,它实现的就是 key-value 之间的映射关系,主要提供的方法包括 Add、Lookup、Delete 等。因为哈希表是一种基础的数据结构,每个 key 都会有一个唯一的索引值,通过索引可以很快地找到对应的值,所以使用它进行数据的插入和读取是很快的。Go 语言本身就内建了这样一个数据结构,也就是 map 数据类型。
1. 线程安全的 map
Go 语言内建的 map 类型如下:
map[k]V
其中,key 类型的 K 必须是可比较的(comparable),也就是可以通过 == 和 != 操作符进行比较;V 的值和类型无所谓,它可以是任意类型,或者为 nil。在 Go 语言中,bool、整数、浮点数、复数、字符串、指针、Channel、接口都是可比较的,包含可比较元素的 struct 和数组也是可比较的,而 sliece、map、函数值都是不可比较的。
那么,上面这些可比较的数据类型都适合作为 map 的 key 的类型吗?答案是否定的。在通常情况下,我们会选择内建的基本类型,比如整数、字符串作为 key 的类型,因为这样最方便,值不可变,也不容易出错。而使用 struct 作为 key 的类型,如果 struct 的某个字段值被修改了,那么在查询 map 时将无法获取它添加的值。
type mapKey struct {
key int
}
func mail() {
var m = make(map[mapKey]string)
var key = mapKey{10}
m[key] = "hello"
fmt.Printf("m[key]=%s\n", m[key])
// 修改 key 的字段值后再次查询map,将无法获取刚才添加的值
key.key = 100
fmt.Printf("再次查询m[key]=%s\n", m[key])
}
那该怎么办呢?如果使用 struct 作为 key 的类型,则要保证 struct 对象在逻辑上是不可变的,这样才能保证 map 的逻辑没有问题。以上就是在选择 key 类型时需要注意的地方。
接下来,我们看一下使用 map[key] 函数时需要注意的一个知识点。在 Go 语言中, map[key] 函数的返回结果可以是一个值,也可以是两个值,这是容易让人迷惑的地方。原因在于:如果获取一个不存在的 key 对应的值,则会返回零值。为了区分真正的零值和 key 不存在这两种情况,可以根据第二个返回值来判断,如下面代码:
func main(){
var m = make(map[string]int)
m ["a"] = 0
fmt.Printf("a=%d;b=%d\n",m["a"],m["b"])
av,aexisted := m["a"]
bv,bexisted := m["b"]
fmt.Printf("a=%d,existed:%t;b=%d,existed:%t\n",av,aexisted,bv,bexisted)
}
将对 map 的遍历故意设置成无序的&#