
map简介
在Go语言中,一个map就是一个哈希表的引用。也就是说,map数据类型的底层实现是哈希表。所有map的很多特性要在理解底层哈希表的基础之上才能更好的理解。本文先总结go语言中map的基本使用方式,然后提出一些map相关的问题,这些问题在理解哈希表之后即可解决。
Map基本使用
package mainimport "fmt"func main() {elements := make(map[string]string)//make语创建mapelements["h"] = "hello"//赋值emptyMap := map[string]float64{}//创建空mapmyMap := 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 []stringfor 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]intfmt.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==trueif !ok { /*键不存在时的执行逻辑*/ }
关于Map,有下面几个问题:
- 字典的键类型不能是哪些类型
- 应该优先考虑哪些类型作为字典的键类型
- 字典类型的值是并发安全的吗?
第一问
1.对于第一个问题,标准答案是,字典的键不能是不支持 ==和!=运算符的类型,例如,slice,函数,字典类型。
Go 语言的字典类型其实是一个哈希表(hash table)的特定实现,在这个实现中,键和元素的最大不同在于,键的类型是受限的,而元素却可以是任意类型的。
而go语言哈希表的大致实现过程是:
1. 计算键的哈希值1. 一个哈希表会持有一定数量的桶(bucket)1. 根据哈希值在哈表中找到对应的桶(bucket)1. 此时,在哈希桶中,用键值本身去逐个对比元素
只所以,最终还要使用键值本身进行对比,是因为不同值的哈希值是可能相同的。这有个术语,叫做“哈希碰撞”。
综上所述,键的值一定要可以支持==运算才可以。
第二问:
求哈希和判等操作的速度越快,对应的类型就越适合作为键类型
不建议用字典、结构体等高级数据类型作为键
优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。
第三问:
非原子操作需要加锁, map并发读写需要加锁,map操作不是并发安全的,判断一个操作是否是原子的可以使用 go run race 命令做数据的竞争检测
(在并发章节在举例说一下)
