map简介
在Go语言中,一个map就是一个哈希表的引用。也就是说,map数据类型的底层实现是哈希表。所有map的很多特性要在理解底层哈希表的基础之上才能更好的理解。本文先总结go语言中map的基本使用方式,然后提出一些map相关的问题,这些问题在理解哈希表之后即可解决。
Map基本使用
package main
import "fmt"
func main() {
elements := make(map[string]string)//make语创建map
elements["h"] = "hello"//赋值
emptyMap := map[string]float64{}//创建空map
myMap := map[int]int{1: 1, 2: 2, 4: 3}//按字面量赋值
var nilMap map[int]string
//nilMap[3] = "three"
//空的字典不能够直接添加元素
nilMap = make(map[int]string)
nilMap[3] = "three"
delete(nilMap,3)//删除元素
_=&elements["h"]//map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作
fmt.Println(elements)
fmt.Println(myMap[1])
fmt.Println(myMap)
fmt.Println(emptyMap)
fmt.Println(nilMap)
}
Map默认遍历
ages=make(map[string]int){
"alice":31,
"tom":26,
}
for name,age:=range ages{
fmt.Printf("%s\t%d\n",name,age)
}
Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序
Map顺序遍历
如果需要按顺序遍历key/value对,我们必须显式地对key进行排序
import "sort"
var names []string
for name := range ages {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%s\t%d\n", name, ages[name])
}
Map nil值
var ages map[string]int
fmt.Println(ages == nil) // "true"
fmt.Println(len(ages) == 0) // "true"
ages["carol"] = 21 // panic: assignment to entry in nil map
一定要牢记一点,map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常。
这是因为map类型的零值是nil,也就是没有引用任何哈希表。
在向空的map存数据前必须先创建map.
ages=make(map[string]int)//使用make函数创建map,并对空字典ages重新赋值
Map 索引
通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在,那么将得到value对应类型的零值。
那么,如何判断索引是否存在,可以使用ok值
age, ok := ages["bob"]//存在时,ok==true
if !ok { /*键不存在时的执行逻辑*/ }
关于Map,有下面几个问题:
- 字典的键类型不能是哪些类型
- 应该优先考虑哪些类型作为字典的键类型
- 字典类型的值是并发安全的吗?
第一问
1.对于第一个问题,标准答案是,字典的键不能是不支持 ==和!=运算符的类型,例如,slice,函数,字典类型。
Go 语言的字典类型其实是一个哈希表(hash table)的特定实现,在这个实现中,键和元素的最大不同在于,键的类型是受限的,而元素却可以是任意类型的。
而go语言哈希表的大致实现过程是:
1. 计算键的哈希值
1. 一个哈希表会持有一定数量的桶(bucket)
1. 根据哈希值在哈表中找到对应的桶(bucket)
1. 此时,在哈希桶中,用键值本身去逐个对比元素
只所以,最终还要使用键值本身进行对比,是因为不同值的哈希值是可能相同的。这有个术语,叫做“哈希碰撞”。
综上所述,键的值一定要可以支持==运算才可以。
第二问:
求哈希和判等操作的速度越快,对应的类型就越适合作为键类型
不建议用字典、结构体等高级数据类型作为键
优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。
第三问:
非原子操作需要加锁, map并发读写需要加锁,map操作不是并发安全的,判断一个操作是否是原子的可以使用 go run race 命令做数据的竞争检测
(在并发章节在举例说一下)