• 爱上开源之golang入门至实战第四章-映射(Map)


    前言

    键值对的集合是很多语言都支持的基础数据结构,GO语言里也有同样的键值对集合的数据类型,那就是Map。

    Map 是一种无序的键值对的集合。Map可以通过 key 来快速检索数据,key类似于索引,指向数据的值。我们可以像迭代数组和切片那样迭代它。但是因为 Map 是使用 hash 表来实现的;Map 是无序的,无法决定它的返回顺序。Map和切片一样都是Go语言编程里使用非常广泛的数据结构,我们也要熟悉的掌握Map数据类型的使用。

     

    今天就来谈谈go语言里的Map

    4.3.1 声明

    map 是引用类型,可以使用如下声明:

    1. var variable_name map[keytype]valuetype
    2. 例如
    3. var students map[string]Student

    keytype定义的数据类型可以是任意可以用 == 或者 != 操作符比较的类型,k1 == k2 时,可认为 k1 和 k2 是同一个 key。Go 语言中只要是可比较的类型都可以作为 key。除开 slice,map,functions 这几种类型,其他类型都是作为key的对象的。具体包括:布尔值、数字、字符串、指针、通道、接口类型、结构体、只包含上述类型的数组。结构体的比较需要结构体定义的字段类型都支持可比较。如果是结构体,只有 hash 后的值相等以及字面值相等,才被认为是相同的 key。很多字面值相等的,hash出来的值不一定相等,比如引用。

    valuetype可以是任何数据类型,任何类型都可以作为value;通过使用空接口类型, 比如 any, interface{} 我们可以存储任意值,也可以使用函数类型来定义成值类型;这个经常在一些复杂的命令控制程序代码里出现;

    在声明的时候不需要指定map 的长度(创建和初始化时,可以指定初始长度),map 是可以动态增长的。

    如果不初始化 map,那么就会创建一个值为nil的Map对象。nil Map对象不能用来存放键值对。程序运行时,对nil Map对象进行访问时,就会报panic错误。 所以如果要使用一个Map对象,一定要进行初始化或者赋值。这一点上和Slice稍微有点区别。

    4.3.2 初始化

    map 可以用 {key1: val1, key2: val2} 的描述方法来初始化,就像数组和结构体一样。

    1. var m map[string]string
    2. m = map[string]string{"Go": "Google", "Java": "Oracle"}
    3. fmt.Println(m)
    4. ==== OUTPUT ====
    5. map[Go:Google Java:Oracle]

    或者直接写成

    1. m := map[string]string{"Go": "Google", "Java": "Oracle"}
    2. fmt.Println(m)
    3. ==== OUTPUT ====
    4. map[Go:Google Java:Oracle]

    下面是一个结构体作为Value的初始化

    1. students := map[int]struct {
    2. name string
    3. sex  int
    4. age  int
    5. }{
    6. 1: {"user1", 1, 20},
    7. 2: {"user2", 2, 20},
    8. 3: {"user2", 2, 21},
    9. }
    10. fmt.Println(students)
    11. ==== OUTPUT ====
    12. map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21}]

    map 是 引用类型 的: 内存用 make 方法来分配。可以通过make方法来对map进行初始化;

    1. c := make(map[string]string)
    2. c["Go"] = "Google"
    3. c["Java"] = "Oracle"
    4. fmt.Println(c)
    5. ==== OUTPUT ====
    6. map[Go:Google Java:Oracle]
     
    

    1. s := make(map[int]any)
    2. s[1] = struct {
    3. name string
    4. sex  int
    5. age  int
    6. }{"user1", 1, 20}
    7. s[2] = struct {
    8. name string
    9. sex  int
    10. age  int
    11. }{"user2", 2, 20}
    12. s[3] = struct {
    13. name string
    14. sex  int
    15. age  int
    16. }{"user2", 2, 21}
    17. fmt.Println(s)
    18. ==== OUTPUT ====
    19. map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21}]

    当 map 到容量上限的时候,如果再增加新的键值对,map 的大小会自动加 1。出于对性能的考虑,对于大的 map或者会快速扩张的 map,即使不是非常明确具体的容量大小,也最好先根据初略的计算,大概的标明Map的大小。 在使用make初始化Map的时,可以预先给Map指定⼀个合理元素数量,在初始化时先申请⼀⼤块内存,而可以避免后续操作时频繁扩张。

    例如

    map2 := make(map[string]float32, 100) 

    4.3.3 赋值及访问

    map在中的对象可以通过key进行赋值或者访问

    1. s := make(map[int]any)
    2. s[1] = struct {
    3. name string
    4. sex  int
    5. age  int
    6. }{"user1", 1, 20}
    7. s[2] = struct {
    8. name string
    9. sex  int
    10. age  int
    11. }{"user2", 2, 20}
    12. s[3] = struct {
    13. name string
    14. sex  int
    15. age  int
    16. }{"user2", 2, 21}
    17. fmt.Println(s[0])
    18. fmt.Println(s[1])
    19. fmt.Println(s)
    20. ==== OUTPUT ====
    21. <nil>
    22. {user1 1 20}
    23. map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21}]

    如上个例子所示,s为Map对象,其中key值分别有1,2,3;访问key值为0的时候,key不存在;返回nil;

    访问Map里不存在的Key返回nil对象; 由于Map的value可以是任何类型的值,也包括nil,所以我们可以将一个nil对象作为value放入Map对象, 时候返回值也为nil,但是key是存在的; 我们可以通过下面的方式来进行key的判断;一般判断是否某个key存在,不使用值判断,

    1. s := make(map[int]any)
    2. s[1] = struct {
    3. name string
    4. sex  int
    5. age  int
    6. }{"user1", 1, 20}
    7. s[2] = struct {
    8. name string
    9. sex  int
    10. age  int
    11. }{"user2", 2, 20}
    12. s[3] = struct {
    13. name string
    14. sex  int
    15. age  int
    16. }{"user2", 2, 21}
    17. s[4] = nil
    18. fmt.Println(s)
    19. fmt.Println(s[1])
    20. fmt.Println(s[0])
    21. fmt.Println(s[4])
    22. if v, ok := s[0]; !ok {
    23. fmt.Println("不存在")
    24. } else {
    25. fmt.Printf("存在 %+v \n", v)
    26. }
    27. if v, ok := s[4]; !ok {
    28. fmt.Println("不存在")
    29. } else {
    30. fmt.Printf("存在 值=%+v \n", v)
    31. }
    32. if v, ok := s[1]; !ok {
    33. fmt.Println("不存在")
    34. } else {
    35. fmt.Printf("存在 值= %+v \n", v)
    36. }
    37. ==== OUTPUT ====
    38. map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21} 4:<nil>]
    39. {user1 1 20}
    40. <nil>
    41. <nil>
    42. 不存在
    43. 存在 值=<nil>
    44. 存在 值= {name:user1 sex:1 age:20}

    4.3.4 迭代Map

    非常多的时候,我们需要遍历整个Map所有的Key,并根据获取到的Key和对应的Value对象进行操作;此时我们的可以使用range方式来实现这个目的, 例如

    1. s := make(map[int]any)
    2. s[1] = struct {
    3. name string
    4. sex  int
    5. age  int
    6. }{"user1", 1, 20}
    7. s[2] = struct {
    8. name string
    9. sex  int
    10. age  int
    11. }{"user2", 2, 20}
    12. s[3] = struct {
    13. name string
    14. sex  int
    15. age  int
    16. }{"user2", 2, 21}
    17. s[4] = nil
    18. fmt.Println(s)
    19. for k, o := range s {
    20. fmt.Printf("%d=%+v \n", k, o)
    21. }
    22. ==== OUTPUT ====
    23. map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21} 4:<nil>]
    24. 1={name:user1 sex:1 age:20}
    25. 2={name:user2 sex:2 age:20}
    26. 3={name:user2 sex:2 age:21}
    27. 4=<nil>

    由于Map是无序的,迭代的过程不能保证返回次序,返回的顺序;通常是和具体环境和版本实现有关。

    4.3.5 值传递和引用传递

    Map对象本身,在作为参数和返回值的时候,都是使用的引用传递;如下面的示例

    1. setName := func(a map[string]string, name string) map[string]string {
    2. a["name"] = name
    3. return a
    4. }
    5. sample := map[string]string{"name": "Go"}
    6. fmt.Println(sample) // map[name:Go]
    7. setName(sample, "Java")
    8. fmt.Println(sample) // map[name:Java]
    9. two := setName(sample, "C#")
    10. fmt.Println(two) // map[name:C#]
    11. two["name"] = "Python"
    12. fmt.Println(sample) // map[name:Python]
    13. ==== OUTPUT ====
    14. map[name:Go]
    15. map[name:Java]
    16. map[name:C#]
    17. map[name:Python]

    与切片一样,切片有 copy函数来进行值的复制;map 却没有,目前只能通过遍历然后重新赋值来实现。

    通过Map的Key访问获取的值对象,不是引用传递,是指传递

    如下列所示

    1. type student struct {
    2. name string
    3. sex  int
    4. age  int
    5. }
    6. s1 := make(map[int]student)
    7. s1[1] = student{"user1", 1, 20}
    8. s1[1].name = "User01"   //Cannot assign to s1[1].name
    9. ==== Complie ERROR====
    10. .\build_test.go:134:2: cannot assign to struct field s1[1].name in map

    通过range的方式,进行迭代,或者值对象也是值传递

    1. type student struct {
    2. name string
    3. sex  int
    4. age  int
    5. }
    6. s1 := make(map[int]student)
    7. s1[1] = student{"user1", 1, 20}
    8. setName2 := func(a map[int]student, id int, newName string) map[int]student {
    9. if v, ok := a[id]; ok {
    10. v.name = newName
    11. }
    12. return a
    13. }
    14. fmt.Println(s1) // map[1:{user1 1 20}]
    15. setName2(s1, 1, "User01")
    16. fmt.Println(s1) // map[1:{user1 1 20}]
    17. s2 := setName2(s1, 1, "User02")
    18. fmt.Println(s2) // map[1:{user1 1 20}]
    19. ==== OUTPUT ====
    20. map[1:{user1 1 20}]
    21. map[1:{user1 1 20}]
    22. map[1:{user1 1 20}]

    如果需要进行原值的覆盖可以使用完整替换 value 或使⽤指针

    1. type student struct {
    2. name string
    3. sex  int
    4. age  int
    5. }
    6. s1 := make(map[int]student)
    7. s1[1] = student{"user1", 1, 20}
    8. s1[1] = student{"User01", 1, 20}
    9. fmt.Println(s1) // map[1:{User01 1 20}]
    10. ==== OUTPUT ====
    11. map[1:{User01 1 20}]
    12. type student struct {
    13. name string
    14. sex  int
    15. age  int
    16. }
    17. ss := make(map[int]*student)
    18. ss[1] = &student{"user1", 1, 20}
    19. setName3 := func(a map[int]*student, id int, newName string) {
    20. if v, ok := a[id]; ok {
    21. v.name = newName
    22. }
    23. }
    24. fmt.Println(ss[1]) //{user1 1 20}
    25. setName3(ss, 1, "User01")
    26. fmt.Println(ss[1]) // {User01 1 20}
    27. ==== OUTPUT ====
    28. &{user1 1 20}
    29. &{User01 1 20}

    4.3.6 Delete

    delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。从 map1 中删除 key1:

    直接 delete(map1, key1) 就可以。如果 key1 不存在,该操作不会产生错误。

    实例如下:

    Delete(map4, "a")

    4.3.7 map的GC回收机制

    map是只增不减的一种数组结构,那些被 delete 的 key 会被打上标记,但是不会被GC回收。对于大的map对象。如果不再使用了最好使用赋值 nil 的方式使其整个的可以被GC。

    4.3.8 并发缺陷

    Go语言原生的Map非并发安全的, 在多并发的情况下,如果有写的操作,会出现Panic,提示concurrent map writes的错误

    1. mm := map[int]int{}
    2. for i := 0; i < 21; i++ {
    3. go func() {
    4. mm[1] = 1
    5. }()
    6. }
    7. ====Panic====
    8. fatal error: concurrent map writes

    另外如果多线程同时 read 和 write ,或者删除 key,还会出现 fatal error: concurrent map read and map write,这都是 map 存在的并发问题。

    解决问题,可以引入sync.Mutex或者sync.RWMutex控制并发访问;或是是通过第三方的并发控制进行解决。

  • 相关阅读:
    大数据运维实战第二十四课 Yarn 资源调度 Fair Schedule 与 Capacity Scheduler 配置选型
    如何证明特征值的几何重数不超过代数重数
    Ant Design for Figma设计系统组件库 支持变量 非社区版
    【弱监督学习】Learning from Incomplete and Inaccurate Supervision
    神探大战观影解说
    CentOS 7:dmPython安装及测试连接达梦数据库
    【LabVIEW学习篇 - 12】:通知器
    【Java】springboot 枚举参数
    vue el-dialog封装成子组件(组件化)
    office的文件(word、excel、ppt)图标变白
  • 原文地址:https://blog.csdn.net/inthirties/article/details/126281967