美文网首页
Go语言 映射(map)

Go语言 映射(map)

作者: 小杰的快乐时光 | 来源:发表于2018-08-18 22:36 被阅读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 语言实战》

相关文章

网友评论

      本文标题:Go语言 映射(map)

      本文链接:https://www.haomeiwen.com/subject/ckjqiftx.html