Map 是一种无序的键值对的集合(=**一种无序的基于key-value的数据结构**)。

使用:

  • Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
  • Go语言中的map是引用类型,必须初始化才能使用。
  • Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

基本语法

声明/初始化/赋值

全局 局部
声明 var a <font style="color:rgb(0, 134, 179);">map</font>[string]int
范围 var a a :=
初始化为零值 等式右边
**<font style="color:rgb(51, 51, 51);">make</font>**(**<font style="color:rgb(51, 51, 51);">map</font>**[string]int, 8)/<font style="color:#c7773e;">map</font>[<font style="color:#c7773e;">int</font>]<font style="color:#c7773e;">int</font>{}
初始化 ?集合/映射/字典 map - 图1
或者
下标赋值map_01[key] = ....(value)
赋值 赋值都在函数块内进行(声明之后)
1. map_01 = map[string]int {...:..., ...:..., } 注意,map[key_type] value_type是数据的一部分,和python用{}[]区别很不一样
2. map_01[key] = ....(value) 下标赋值

:::info 要求

:::

  1. key在集合中是唯一的,定义之后不可修改的,value是可修改的
  2. 初始化
    1. 集合使用的要求——必须初始化才能使用,原因:指针,初始化有两种方式,集合使用前一定会经过两者其中一步:
      1. 声明时直接赋值,可为零值
      2. 通过make函数进行内存分配
    2. 若未初始化,则默认为nil
      这里需要注意,经过make初始化得到的map,其key和value为对应其类型的零值,如int—0,char—“”,和nil是不一样的

:::info make函数创建map

:::

  1. 语法
    1. <font style="color:rgb(36, 41, 46);">map_1 := make([key_type]value_type,cap)</font>
    2. 其中cap表示map的容量,cap可省略,但是我们应该在初始化map的时候就为其指定一个合适的容量。避免不断地扩容,会引起底层数据的不断创建与赋值,最好在开始的时候就赋予好容量
  2. 集合map是不能求cap的(其实我内心非常的无语)
    1. make函数设的是cap
    2. 计算长度,可用<font style="color:rgb(36, 41, 46);">len(map_1)</font> ,实际多少就多少;用make函数构建map可以设cap,但不可用 <font style="color:rgb(36, 41, 46);">cap(map_1)</font>求cap
      ?集合/映射/字典 map - 图2
由于map是自动扩容的,我们通常也不关系map的cap,只关心len

:::info 值和类型

:::

比如我们来分析map_1 := map[string]int{"justin": 1, "Bob":2, "Tom": 3}

  1. 类型——map[string]int
  2. 值——map[string]int{"justin": 1, "Bob":2, "Tom": 3} 注意和python(用符号区分)不一样的是,开头的类型,也是值的一部分
  3. 有一个类型需要注意,那就是结构类型

调用

  1. 无range
    1. v = map_01[key]
    2. <font style="color:#AD1A2B;">k, v = map_01[key]</font>是错误的!!!<font style="color:#AD1A2B;">value, ok := map_1[key]</font>才是对的!!!用来判断键值是否存在
  2. 有range
    1. 但是<font style="color:#AD1A2B;">for k,v := range map_1{...}</font>k和v是获得的分别是key和value
    2. 也可以<font style="color:#AD1A2B;">for k := range map_1{...}</font>,等价于<font style="color:#AD1A2B;">for k,_ := range map_1{...}</font>

修改

修改已有键值对

map_1[defined_key] = new_value

如果value是结构体或者数组,可以整体替换value,但不可以修改,详情

添加

map_1[undefined_key] = undefined_value且会自动扩容

删除

使用delete()函数删除键值对——**<font style="color:rgb(51, 51, 51);">delete</font>**(**<font style="color:rgb(51, 51, 51);">map</font>**, **<font style="color:rgb(51, 51, 51);">key</font>**) NOTE:字典不会收缩内存 ## 应用 ### map的遍历 使用for range循环,其遍历结果是无序的 go func main() { scoreMap := make(map[string]int) scoreMap["张三"] = 90 scoreMap["小明"] = 100 scoreMap["王五"] = 60 for k, v := range scoreMap { fmt.Println(k, v) } } 1. 至于想不想要key和value 1. 一方面可以用_下划线省略其中一个 2. 或者,只想要k还可以:
?集合/映射/字典 map - 图3 2. 打印key和value,记得对应其数据类型输出就好 :::info 按照指定顺序遍历map

:::

  1. func main() {
  2. rand.Seed(time.Now().UnixNano()) //初始化随机数种子
  3. var scoreMap = make(map[string]int, 200)
  4. for i := 0; i < 100; i++ {
  5. key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
  6. value := rand.Intn(100) //生成0~99的随机整数
  7. scoreMap[key] = value
  8. }
  9. //取出map中的所有key存入切片keys
  10. var keys = make([]string, 0, 200)
  11. for key := range scoreMap {
  12. keys = append(keys, key)
  13. }
  14. //对切片进行排序
  15. sort.Strings(keys)
  16. //按照排序后的key遍历map
  17. for _, key := range keys {
  18. fmt.Println(key, scoreMap[key])
  19. }
  20. }

判断某个键值对是否存在

:::info 看一个现象

:::

  1. package main
  2. import "fmt"
  3. func main() {
  4. scoreMap := make(map[string]int)
  5. scoreMap["小明"] = 100
  6. scoreMap["小王"] = 0
  7. x := scoreMap["张三"]//未定义
  8. y := scoreMap["小王"]//定义值为0
  9. fmt.Printf("x=%d, y=%d",x, y)
  10. }

结果

?集合/映射/字典 map - 图4

结论:

不存在的键值对,按照 map[key] = value格式,会输出其定义返回值类型的零值(本例子其value类型位int,则输出为0),而不是nil,也不会出错(和其他语言不一样,不存在的键值对输出,go语言并不会报错

由以上看出,不存在的键值对,和存在但设其value为零值的键值对,按照 map[key] = value格式,其value都输出为0,那怎么办呢?看下方

:::info 那怎么判断这个键值对存不存在呢?

:::

语法:value, ok := map_1[key]
(注意这次value是在前面,ok获得的只能是true/false,无论value是什么类型)

如果key存在,则ok为true,value为对应的值;不存在ok为false,value为值类型的零值

  1. package main
  2. import "fmt"
  3. func main() {
  4. scoreMap := make(map[string]int)
  5. scoreMap["小明"] = 100
  6. // 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
  7. v, ok := scoreMap["张三"]//未定义键值
  8. if ok {
  9. fmt.Println(v)
  10. } else {
  11. fmt.Println("查无此人")
  12. }
  13. fmt.Println(ok)
  14. }

结果

?集合/映射/字典 map - 图5

map做函数参数

引用传递

?集合/映射/字典 map - 图6

组合

value为结构体或者数组

:::info 如果value是结构体或者数组,那就不一样了,因为内存访问安全和哈希算法等缘故,字典被设计成“not address”,故不能直接修改value成员

:::

  1. 这样是可以的
    ?集合/映射/字典 map - 图7
  2. 这样就不行
    ?集合/映射/字典 map - 图8
  1. package main
  2. import "fmt"
  3. func main() {
  4. type user struct{
  5. name string
  6. age byte
  7. }
  8. map_1 := map[int]user{
  9. 1:{"Tom", 19},
  10. }
  11. map_1[1] = user{"justin", 25}
  12. fmt.Println(map_1)
  13. }
  1. 那对于第二种情况,怎么办呢?——两个办法
    1. 返回整个value给一个变量u,进行修改,然后再替换掉原value
      ?集合/映射/字典 map - 图9
  1. package main
  2. import "fmt"
  3. func main() {
  4. type user struct{
  5. name string
  6. age byte
  7. }
  8. map_1 := map[int]user{
  9. 1:{"Tom", 19},
  10. }
  11. u := map_1[1]
  12. u.age +=1
  13. map_1[1] = u
  14. fmt.Println(map_1)
  15. }
  1. 2. 使用指针结构体做value

?集合/映射/字典 map - 图10

  1. package main
  2. import "fmt"
  3. func main() {
  4. type user struct{
  5. name string
  6. age byte
  7. }
  8. map_1 := map[int]*user{
  9. 1:&user{"Tom", 19},
  10. }
  11. map_1[1].age++ //(*map_1)[1].age++ 也行,Go语言给的结构体指针的特殊使用性质
  12. fmt.Println(*map_1[1])
  13. }

:::info value为数组

:::

使用数组

?集合/映射/字典 map - 图11

改进方法:推荐使用切片

?集合/映射/字典 map - 图12

元素为map类型的切片

  1. func main() {
  2. var mapSlice = make([]map[string]string, 3)
  3. \\对比一般切片 s:= make([]int,3,3)
  4. \\这里创建了一个元素为集合,元素个数为3的切片
  5. \\未初始化前的打印,注意打印集合用的%v
  6. for index, value := range mapSlice {
  7. fmt.Printf("index:%d value:%v\n", index, value)
  8. }
  9. fmt.Println("after init")
  10. //对切片中的map元素进行初始化
  11. //先用make函数初始化!!!
  12. //第一个切片元素,容量为10
  13. mapSlice[0] = make(map[string]string, 10)
  14. mapSlice[0]["name"] = "王五"
  15. mapSlice[0]["password"] = "123456"
  16. mapSlice[0]["address"] = "红旗大街"
  17. for index, value := range mapSlice {
  18. fmt.Printf("index:%d value:%v\n", index, value)
  19. }
  20. }

?集合/映射/字典 map - 图13

value为切片类型的map

  1. func main() {
  2. //切片len= 3,元素为map
  3. //一般map声明 var s = make(map[string]int, 3),对比,value类型就是[]string
  4. var sliceMap = make(map[string][]string, 3)
  5. fmt.Println(sliceMap)
  6. fmt.Println("after init")
  7. key := "中国"
  8. value, ok := sliceMap[key]
  9. if !ok {
  10. value = make([]string, 0, 2)
  11. }
  12. value = append(value, "北京", "上海")
  13. sliceMap[key] = value
  14. fmt.Println(sliceMap)
  15. }

?集合/映射/字典 map - 图14

我自己修改过的代码

?集合/映射/字典 map - 图15

?Map与工厂模式

  1. Map的value可以是一个方法
  2. 与Go的Docker type接口方式一起,可以方便地实现但一方法对象的工厂模式

?集合/映射/字典 map - 图16

map不是没顺序的吗?

?Set的实现

go没有内置set,但是可以通过map实现,可以 map[type]bool

  1. 元素的唯一性
  2. 基本操作
    1. 添加元素
    2. 判断元素是否存在
    3. 删除元素
    4. 元素个数