GO语言中map是一种键值-元素对的无序集合 声明定义的方式如下:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
map的添加,遍历,判断,删除的例子如下
//创建集合,学生年龄集合
var studentAgeMap map[string]int
studentAgeMap = make(map[string]int)
//预置一部分数据
studentAgeMap["小明"]=14
studentAgeMap["小华"]=15
studentAgeMap["小红"]=13
//遍历map
for k,v := range studentAgeMap{
fmt.Printf("%s的年龄是%d\n",k,v)
}
//检查map是否存在小刚
value,ok := studentAgeMap["小刚"]
if ok{
fmt.Printf("存在小刚,他的年龄是%d\n",value)
}else{
fmt.Println("不存在小刚")
}
//删除小明
delete(studentAgeMap,"小明")
//再次遍历map
for k,v := range studentAgeMap{
fmt.Printf("%s的年龄是%d\n",k,v)
}
GO语言中的map其实是哈希表的特定实现,一个哈希表会持有一定数量的桶(bucket),一个桶可以存放多个绑定在一起的(键值-元素)对,但是一个桶只对应一个哈希值. 假如我们要根据一个键值去找一个元素,哈希表会先用哈希函数将键值转换成哈希值,根据哈希值去定位一个桶,然后将桶中绑定在一起的键值-元素对做一个遍历,将其键值与要找的键值做判等操作,键值相等的那个键值-元素对就是要找的结果.
这里就有一个问题,map的键值类型可以是哪些? 只要能被转出哈希值且能做判等操作的类型都可以编译通过,所以字典类型、函数类型、切片类型不能作为map的键值
另外接口类型最好也不要做为键值类型,如下的例子
var mymap = map[interface{}]int{
"1": 1,
[]int{2}: 2, // 这里编译会通过,但是运行会引发 panic。
3: 3,
}
当然在为map选择键值类型的时候我们也要考虑最优的选择 根据一个键值去找一个元素其实我们要干两件事,第一个是将键值转换成哈希值,第二件事就是桶中的键值-元素对要遍历与所要找的键值做对比,这两件事是比较耗时间的 所以我们应该选择求哈希值快并且判等速度快的类型作为键值类型,通常类型宽度越小的速度越快,类型宽度是指它的单个值所需要占用的字节数,比如bool、int8、unit8的宽度是1,再比如字符串类型的宽度是不定的,如果是高级数据类型比如数组或者结构体,它的哈希值是它包含的每个元素的哈希值合并得到,所以一般不建议使用高级数据类型作为map的键值.
GO语言中map为引用类型,所以我们只声明不初始化的时候,它的值为nil,这个时候查询、删除操作不会报错,添加操作会报错,如下
//只声明集合, studentAgeMap==nil
var studentAgeMap map[string]int
//检查map
value,ok := studentAgeMap["小刚"] //不会报错
if ok{
fmt.Printf("存在小刚,他的年龄是%d\n",value)
}else{
fmt.Println("不存在小刚")
}
//遍历map
for k,v := range studentAgeMap{ //不会报错
fmt.Printf("%s的年龄是%d\n",k,v)
}
//删除操作
delete(studentAgeMap,"小明") //不会报错
//添加操作
studentAgeMap["小华"]=15 //报错:panic: assignment to entry in nil map
所以在字典添加操作的时候需要注意判等map是否是nil
最后一个就是map不是并发安全的,不是原子操作,同一时刻发生对map的读写操作需要加锁,GO语言提供了go run race命令进行数据竞争检测
如下执行结果会报错:"fatal error: concurrent map read and map write"
func main() {
m := map[int]int{1: 1}
go func() {//读操作
for {
_ = m[1]
time.Sleep(1)
}
}()
time.Sleep(time.Second)
go func() {//写操作
for {
m[1] = 1
time.Sleep(1)
}
}()
time.Sleep(30 * time.Second)
fmt.Println(m)
}
使用加锁或者使用sync.Map就不会报错,如下:
func main() {
var scene sync.Map
scene.Store(1, 1)
go func(){
for {
scene.Load(1)
time.Sleep(1)
}
}()
time.Sleep(time.Second)
go func(){
for {
scene.Store(1, 1)
time.Sleep(1)
}
}()
time.Sleep(30 * time.Second)
fmt.Println(scene)
}
最后非常感谢郝林老师,读了他文章,加深了理解