作者:程序员CKeen
博客:http://ckeen.cn
长期坚持做有价值的事!积累沉淀,持续成长,升维思考!希望把编码做为长期爱好😄
在 Go 语言中,map是一种基于哈希表实现的数据结构。哈希表是一种方便且功能强大的内置数据结构,它是一个无序的key/value键值对的集合。
哈希表一般写作map[K]V,其中K代表键,V代表K对应的值。哈希表通过给定的键Key可以在常数时间复杂度内检索、更新或删除对应的value,是一种比较常用的数据结构。
下面主要介绍一下map的具体的使用方法:
1.map的的几种创建方式
- 直接创建一个空map
map1 := map[string]int {} // 先创建一个空map,不包含任何键值对 map1["foo"] = 1 // 添加一个键值对 map1["boo"] = 2 map1["hello"] = 3 fmt.Println(map1) // map[boo:2 foo:1 hello:3] ,可以看到打印的结果是无序的
- 创建一个初始化键值对的map
-
map2 := map[string]int { // 初始化创建一个包含键值对的map "foo": 1, "boo": 2, "hello" : 3, } fmt.Println(map2) // map[boo:2 foo:1 hello:3]
- make内置函数创建。go中所有的引用类型都可以通过make来创建实例
map3 := make(map[string]int) map3["foo"] = 1 map3["boo"] = 2 map3["bar"] = 3 fmt.Println(map3) // map[bar:3 boo:2 foo:1]
2.map的操作
- 对map进行添加键值对操作
创建一个map的集合对象后,只需要直接使用中括号添加key,然后=后面直接赋值即可。map4 := make(map[string]int) map4["foo"] = 1 map4["boo"] = 2 map4["bar"] = 3 map4["bar"] = 4 fmt.Println(map4) // map[bar:4 boo:2 foo:1]
map的key/value使用需要注意的点:
1. map的键可以是定义了相等运算符的任何类型,例如整数、浮点数和复数、字符串、指针、接口(只要动态类型支持相等)、结构体和数组。
2. 切片不能用作映射键,因为它们没有定义相等。
3. 虽然浮点数类型也是支持相等运算符比较的,但是将浮点数用做key类型,最坏的情况是可能出现的NaN和任何浮点数都不相等。
4. map添加的时候如果该key已经存在,则会覆盖该键对应的值,而不会报错。
- 对map进行取值操作
map中进行取值操作,如果key不存在的时候,它不会报错, 它会返回value的零值。而且map中的value值并不是一个变量,因此我们不能对map的元素进行取址操作map4 := make(map[string]int) map4["foo"] = 1 map4["boo"] = 2 map4["bar"] = 3 map4["bar"] = 4 barVal := map4["bar"] fmt.Println(map4,barVal) // map[bar:4 boo:2 foo:1] 4 hello := map4["hello"] fmt.Println(hello) // 打印 0 barVal2 := &map4["bar"] //Cannot take the address of 'map4["bar"]'
- 对map的删除操作
直接使用go的库函数delete就可以完成删除key/value操作,当key不存在的时候,delete操作也不会报错,也没有任何反馈信息。因此如果你想知道是否你删除的键值对是否有删除, 可以先判断该键值对是否存在。map4 := make(map[string]int) map4["foo"] = 1 map4["boo"] = 2 map4["bar"] = 3 delete(map4,"bar") fmt.Println(map4)
- 对map的遍历操作
遍历map中全部的key/value对,使用range风格的for循环实现,和之前的slice遍历语法类似。map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。实际遍历的顺序是随机的,每一次遍历的顺序都不相同。map6 := make(map[string]int) map6["foo"] = 1 map6["boo"] = 2 map6["bar"] = 3 map6["hello"] = 5 for k, val := range map6 { fmt.Printf("key:%v,value:%v\n",k,val) } // key:foo,value:1 // key:boo,value:2 // key:bar,value:3 // key:hello,value:5
如果我们想对map进行排序,则我们必须显式地对key使用slice进行排序,然后根据slice中key的顺序,去map中取值。var keys []string for key := range map4 { keys = append(keys, key) } sort.Strings(keys) for _, val := range map4 { fmt.Printf("%s\t%d\n", val, ages[val]) }
- 判断map的key的是否存在
当前map的键不存在的时候,取到的值默认为零值。这样就存在当取到为零值的时候,无法判断到底是键不存在,还是存得键本身就为零值的情况,所以go利用多返回值的形式,可以来判断是否。map7 := map[string]int { // 初始化创建 "foo": 1, "boo": 2, "hello" : 3, "test" : 0 } fmt.Println(map7["test"],map7["world"]) // 打印的为零值 0 0 if val, ok := map7["world"]; ok { fmt.Println(val) } // 不会打印结果
上述代码中,如果ok为true, 则说明对应的key存在。
为了测试是否存在于映射中而不考虑实际值,可以使用空白标识符(_)来代替通常的值变量。
3.map中使用的时候要注意点
- 与切片一样,map了保存对底层数据结构的引用。如果函数接受map参数,则调用者可以看到它对map元素所做的更改,类似于传递指针。
我们如果不想函数内部修改影响传入的map结构时,先进行复制操作是比较好的做法。同样如果我们结构内部对外的暴露的是一个map结构,如果不想外面的修改影响到内部的mpa结构,也可以将对外暴露的结构进行复制一个新的对外。
- 在使用 map 时,需要注意并发安全性。如果多个 goroutine 同时读写同一个 map,可能会导致数据竞争的问题。可以使用 sync 包中的锁机制来保证并发安全。也可以是用sync.Map结构
- 在使用 map 时,需要注意内存占用。如果 map 中存储了大量的数据,可能会导致内存占用过高,从而影响程序的运行效率。