GO语言字典类型学习

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)
}

最后非常感谢郝林老师,读了他文章,加深了理解

转载于:https://my.oschina.net/jian1991/blog/3098207

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值