Go语言 映射(map)
2018-08-18 本文已影响0人
小杰的快乐时光
Go语言中的 映射(map)是一种内置的数据结构,保存键值对的无序的集合,容量只受机器内存的限制。
对于映射中所有的键规定是唯一的且必须是支持 == 和 != 操作符类型的。
映射是属于引用类型的。
哪怕使用同样的顺序保存键值对,每次迭代时的顺序都可能不一样,因为映射的实现使用了散列表。
映射的创建方式有以下几种:
func main() {
map1 := make(map[string]string) // map1: map[]
map2 := make(map[string]string,5) // map2: map[]
map3 := map[string]string{"num1":"num1","num2":"num2"} // map3: map[num1:num1 num2:num2]
map4 := map[string]string{} // map4: map[]
}
当映射的值为空接口时,表示值可以为任意类型,当我们访问这个值时,可以通过类型开关和类型断言或者类型检视来获取该值的实际类型
map5 := map[string]interface{}{}
map5["num1"] = "num1"
map5["num2"] = 123
fmt.Println("map5: ",map5)
------output-------
map5: map[num1:num1 num2:123]
当映射的键为指针时
func main() {
map1 := map[*Point]string{}
map1[&Point{1,2,3}] = "a"
map1[&Point{4,5,6}] = "b"
map1[&Point{7,8,9}] = "c"
fmt.Println(map1) //这里就会调用我们自己的string方法,而不是输出Point的地址
}
type Point struct {
x,y,z int
}
//重写 Point 的 string 方法
func (point Point)String()string {
return fmt.Sprintf("(%d,%d,%d)",point.x,point.y,point.z)
}
-----output-----
map[(1,2,3):a (4,5,6):b (7,8,9):c]
//若没有自己重写Point 的 string 方法,就会输出下面结果
map[0xc04205e0c0:a 0xc04205e0e0:b 0xc04205e100:c]
使用指针当映射的键时,就可以达到键的“不唯一”性,比如我添加两个相同的键
map1 := map[*Point]string{}
map1[&Point{1,2,3}] = "a"
map1[&Point{1,2,3}] = "a"
fmt.Println(map1)
------output------
map[(1,2,3):a (1,2,3):a]
查询映射集合中的某个键值对
myMap := map[string]int{"1":2,"2":3}
num := myMap["1"] //第一种查询方式,不存在键就返回0
value, ok := myMap["1"] //第二种查询方式:1 表示 键,value表示键对应的值,ok表示是否存在这个键值对
fmt.Println(num)
fmt.Println(value)
fmt.Println(ok)
-----output----- //若不存在就会输出 空,false
2
2
true
删除对应键的map键值对
delete(myMap,"1") //删除键为1的键值对,若不存在键为1的,也不会出现什么问题,无副作用。
//但这个键不能为nil,否则会报错:cannot use nil as type string in delete
根据某个键更新值:利用重复的键会覆盖掉原值的方法
若键重复添加,就会让后者覆盖前者
map5 := map[string]interface{}{}
map5["num1"] = "num1"
map5["num1"] = 123
fmt.Println("map5: ",map5)
------output-------
map5: map[num1:123]
那如果是需要更新某个键呢?
那么可以先查询出旧键的值,然后删除这个键值对,保存新键与旧值
func main() {
myMap := map[string]int{"1":2,"2":3}
fmt.Println("更新前的 map:",myMap)
//需要将键为1的改为5
oldVaule := myMap["1"] //先保存旧键的值
delete(myMap,"1") //删除旧键值对, 这种方法只能用在映射存储的值都是非零值的情况。
myMap["5"] = oldVaule
fmt.Println("更新后的 map:",myMap)
}
-----output-----
更新前的 map: map[1:2 2:3]
更新后的 map: map[5:2 2:3]
使用range迭代映射,这里range返回的就是键值对,而不是索引和值
func main() {
MyBooks := make( map[string]string)
MyBooks["Golang"] = "Gin"
MyBooks["Java"] = "Spring"
for k,v := range MyBooks {
fmt.Println("k: ",k," v: ",v)
}
}
-----output-----
k: Golang v: Gin
k: Java v: Spring
映射的传递
在函数间传递映射并不会制造出该映射的一个副本,跟传递切片类似,对这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改
func main() {
MyBooks := make( map[string]string)
MyBooks["Golang"] = "Gin"
MyBooks["Java"] = "Spring"
fmt.Println("一:",MyBooks)
removeDemo(MyBooks)
fmt.Println("二:",MyBooks)
}
func removeDemo(MyBooks map[string]string) {
delete(MyBooks,"Java")
fmt.Println("三:",MyBooks)
}
----output----
一: map[Golang:Gin Java:Spring]
三: map[Golang:Gin]
二: map[Golang:Gin]
映射反转
如果一个映射的值都是唯一的,且值的类型也是映射所支持的键类型的话 ,我们可以将键与值反转过来
func main() {
myMap1 := map[string]int{"1":2,"2":3}
myMap2 := make(map[int]string,len(myMap1))
fmt.Println("myMap1:",myMap1)
for k,value:= range myMap1 {
myMap2[value] = k
}
fmt.Println("myMap2:",myMap2)
}
-----output-----
myMap1: map[1:2 2:3]
myMap2: map[2:1 3:2]
映射内部实现
我们之前说过映射的实现是使用了散列表,散列表包含一组桶,在存储,删除或者查找键值对的时候就会先选择一个桶。把映射中的键传给散列函数,就能选中对应的桶,这个散列函数的目的是生成一个索引,这个索引最终将键值对分布到所有可用的桶里。
描述散列函数是如何工作的
散列函数是如何工作的.png桶的内部实现
映射使用两个数据结构来存储数据。第一个数据结构是一个数组,内部存储的是用于选择桶的散列键的高八位值。这个数组用于区分每个键值对要存在哪个桶里。第二个数据结构是一个字节数组,用于存储键值对。该字节数组先依次存储了这个桶里所有的键,之后依次存储了这个桶里所有的值。实现这种键值对的存储方式目的在于减少每个桶所需的内存。 桶的内部实现.png
注:图部分选自《Go 语言实战》