写在文章开头
我们再来了解一下关于go语言
的映射,映射即根据指定算法将键值对落地指定的位置的数据结构,这使得我们再进行定位时就像查阅字典一样可以快速检索和操作。
Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。
详解go语言中的map
映射内部实现
映射底层实际是通过对key
的hash
计算得到一个哈希值,然后通过哈希值的低位决定键值对落到哪个桶上。随后再根据哈希值的高8位定位到bucket
中tophash
、key
、values
的索引位置,我们假设高八位计算后为0,那么这就取这三个数组的0索引位置。
这样一来后续的查询操作就可以通过哈希值的高低位定位到位置,理想情况下,映射查询的时间复杂度为O(1)
。
映射的创建和初始化
映射的常见创建方式有两种,一种是make语法,如下代码所示,声明key
为int
,value
为string
类型,后续进行键值对赋值时赋值语句就像操作数组一样:
dict := make(map[int]string)
dict[10] = "xiaoming"
fmt.Println(dict)
这里我们给出上述代码的输出结果:
map[10:xiaoming]
还有一种则是通过字面量的方式完成声明,注意如果按照下述语法进行声明的话,映射的长度就会根据初始化制定的键值对长度而定。
dict := map[string]int{"a": 1, "b": 2, "c": 3}
fmt.Println(dict)
fmt.Println(len(dict))
所以这就是为什么我们输出的第二行是3的原因:
map[a:1 b:2 c:3]
3
如何使用映射
映射的赋值方式上文已经提到过,这里再次演示一下,语法和数组差不多,通过方括号指定key的值,然后赋上value即可:
var dict = make(map[string]int)
dict["key"] = 2
与其他编程语言不同,go语言
访问映射在返回值的同时还会返回当前当前value是否存在,默认情况下假如map
中对应key
的value
不存在的话,得到的value
就是value
类型的0值,以我们的代码为例我们的映射的value
是个bool
,如果这个key
不存在则返回bool
类型的0值
即false
。
//map赋值
var dict = make(map[int]bool)
dict[1] = true
value, exsist := dict[2]
if !exsist {
//输出一个默认的0值
fmt.Println("map键值对不存在", value)
}
映射的迭代
映射的迭代和数组和切片类似,通过range
即可得到对应键值对:
m := map[int]string{
1: "xiaoming",
2: "xiaowang",
3: "xiaowu",
}
//map的遍历
for k, v := range m {
fmt.Println("key:", k, " value: ", v)
}
对应的输出结果如下:
key: 3 value: xiaowu
key: 1 value: xiaoming
key: 2 value: xiaowang
键值对的删除
go
语言内置的删除函数delete
,我们只需通过delete函数传入映射和要删除的key值即可:
//map赋值
m := map[int]string{
1: "xiaoming",
2: "xiaowang",
3: "xiaowu",
}
//删除前的打印
fmt.Println("删除前的打印", m[3])
delete(m, 3)
//删除后的打印
fmt.Println("删除后的打印", m[3])
输出结果:
删除前的打印 xiaowu
删除后的打印
映射在函数间的传递
文章开头已经说明,映射底层的键值对也是通过数组实现的,所以在函数间传递时都是映射这个结构的引用,这就使得函数的形参得到的映射的引用地址,所以我们在函数内部的操作都会直接影响映射的结果:
就像下面这段代码一样,我们通过foo修改m[1]的值最终是会改为外部传入的映射的键值对的:
func main() {
//map赋值
m := map[int]string{
1: "xiaoming",
2: "xiaowang",
3: "xiaowu",
}
fmt.Println(m)
foo(m)
fmt.Println(m)
}
// map底层也是用指针记录数组,所以函数间传递的也是数组的指针,修改后外部的map也会被修改
func foo(m map[int]string) {
m[1] = "jack"
}
// map底层也是用指针记录数组,所以函数间传递的也是数组的指针,修改后外部的map也会被修改
func foo(m map[int]string) {
m[1] = "jack"
}
输出结果:
map[1:xiaoming 2:xiaowang 3:xiaowu]
map[1:jack 2:xiaowang 3:xiaowu]
小结
本文通过几个简单的实践案例带你了解map的基本特性和使用技巧,希望对你有帮助。
我是 sharkchili ,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。
参考
《go语言实战》