文章目录

  • 1.默认初始值为 nil
  • 2.range 顺序的随机性
  • 3.引用传递
  • 4.元素不可取址
  • 5.并发读写问题
  • 参考文献

map 使用起来非常方便,但也有些必须要注意的地方,否则可能会导致程序异常甚至 panic。

1.默认初始值为 nil

map 未初始化的情况下值为 nil,此时进行取值,返回的是对应类型的零值,不会引发 panic。所以取值时如果不关心取的是否是零值,那么可以直接取而不用使用 comma-ok 式,这样会使代码变得简洁许多。

var mapName map[string]string// 使用 comma-ok 式
if v, ok := mapName["dable"]; ok {name = v
}// 直接取(不关心是否存在)
name = mapName["dable"]

向 map 写入要非常小心,因为向未初始化的 map(值为 nil)写入会引发 panic,所以向 map 写入时需先进行判空操作。

var m map[string]string
m["dable"] = "male"

上面的代码将产生 panic: assignment to entry in nil map 的运行时错误。

2.range 顺序的随机性

map 在没有被修改的情况下,使用 range 多次遍历 map 时输出的 key 和 value 的顺序可能不同。这是 Go 语言的设计者们有意为之,在每次 range 时的顺序被随机化,旨在提示开发者们,Go 底层实现并不保证 map 遍历顺序稳定,请大家不要依赖 range 遍历结果顺序。Golang 官方博文对此有详细说明:Go maps in action。

参考如下程序:

package mainimport ("fmt"
)func main() {fmt.Println("first range:")for i, v := range m {fmt.Printf("m[%v]=%v ", i, v)}fmt.Println("\nsecond range:")for i, v := range m {fmt.Printf("m[%v]=%v ", i, v)}
}

运行输出的结果可能是:

first range:
m[3]=c m[4]=d m[1]=a m[2]=b
second range:
m[1]=a m[2]=b m[3]=c m[4]=d

map 本身是无序的,且遍历时顺序还会被随机化,如果想顺序遍历 map,需要对 map key 先排序,再按照 key 的顺序遍历 map。

import "sort"var tmpSl []string
// 把 key 单独取出放到切片
for k := range m {tmpSl = append(tmpSl, k)
}
// 排序切片
sort.Strings(tmpSl)
// 以切片中的 key 顺序遍历 map 就是有序的了
for _, k := range tmpSl {fmt.Println(k, m[k])
}

3.引用传递

Golang 中没有引用传递,只有值和指针传递。所以 map 作为函数实参传递时本质上也是值传递,只不过因为 map 底层数据结构是通过指针指向实际的元素存储空间,在被调函数中修改 map,对调用者同样可见,所以 map 作为函数实参传递时表现出了引用传递的效果。因此,传递 map 时,函数形参无需使用指针。

考察如下程序:

package mainimport ("fmt"
)func main() {m := map[int32]string{1: "a",2: "b",3: "c",4: "d",}modifyMapV0(m)fmt.Println("after modifyMapV0 pass by value:")for i, v := range m {fmt.Printf("m[%v]=%v ", i, v)}modifyMapV1(&m)fmt.Println("\nafter modifyMapV1 pass by pointer:")for i, v := range m {fmt.Printf("m[%v]=%v ", i, v)}
}// modifyMapV0 删除所有 key 为偶数的元素
// 使用值传递 map
func modifyMapV0(m map[int32]string) {for i := range m {if i%2 == 0 {delete(m, i)}}
}// modifyMapV1 删除所有 key 为大于 1 的元素
// 使用指针传递 map
func modifyMapV1(m *map[int32]string) {for i := range *m {if i > 1 {delete(*m, i)}}
}

运行输出:

after modifyMapV0 pass by value:
m[1]=a m[3]=c
after modifyMapV1 pass by pointer:
m[1]=a

可见值传递同样可以修改 map 的内容,达到了指针传递的效果。所以如果想修改 map 的内容而不是 map 变量本身,那么请使用值传递,而不是指针传递,这样会使代码更加简洁可读。

4.元素不可取址

map 中的元素并不是一个变量,而是一个值。因此,我们不能对 map 的元素进行取址操作。

var m = map[int]int {0 : 0,1: 1,
}func main() {fmt.Println(&m[0])
}

运行报错:

cannot take the address of m[0]

因此,当 map 的元素为结构体类型的值,那么无法直接修改结构体中的字段值。考察如下示例:

package mainimport ("fmt"
)type person struct {name   stringage    byteisDead bool
}func whoIsDead(personMap map[string]person) {for name, _ := range personMap {if personMap[name].age < 50 {personMap[name].isDead = true}   }
}func main() {p1 := person{name: "zzy", age: 100}p2 := person{name: "dj", age: 99} p3 := person{name: "px", age: 20} personMap := map[string]person{p1.name: p1, p2.name: p2, p3.name: p3, }   whoIsDead(personMap)for _, v :=range personMap {if v.isDead {fmt.Printf("%s is dead\n", v.name)}    }
}

编译报错:

cannot assign to struct field personMap[name].isDead in map

原因是 map 元素是无法取址的,也就说可以得到 personMap[name],但是无法对其进行修改。解决办法有二,一是 map 的 value用 struct 的指针类型,二是使用临时变量,每次取出来后再设置回去。

(1)将 map 中的元素改为 struct 的指针。

package mainimport ("fmt"
)type person struct {name   stringage    byteisDead bool
}func whoIsDead(people map[string]*person) {for name, _ := range people {if people[name].age < 50 {people[name].isDead = true}   }
}func main() {p1 := &person{name: "zzy", age: 100}p2 := &person{name: "dj", age: 99} p3 := &person{name: "px", age: 20} personMap := map[string]*person {p1.name: p1, p2.name: p2, p3.name: p3, }   whoIsDead(personMap)for _, v :=range personMap {if v.isDead {fmt.Printf("%s is dead\n", v.name)}    }
}

输出结果:

px is dead

(2)使用临时变量覆盖原来的元素。

package mainimport ("fmt"
)type person struct {name   stringage    byteisDead bool
}func whoIsDead(people map[string]person) {for name, _ := range people {if people[name].age < 50 {tmp := people[name]tmp.isDead = truepeople[name] = tmp }   }
}func main() {p1 := person{name: "zzy", age: 100}p2 := person{name: "dj", age: 99} p3 := person{name: "px", age: 20} personMap := map[string]person {p1.name: p1, p2.name: p2, p3.name: p3, }   whoIsDead(personMap)for _, v :=range personMap {if v.isDead {fmt.Printf("%s is dead\n", v.name)}    }
}

输出结果:

px is dead

5.并发读写问题

共享 map 在并发读写时需要加锁。先看错误示例:

package mainimport ("fmt""time"
)var m = make(map[int]int)func main() {//一个go程写map go func(){for i := 0; i < 10000; i++ {m[i] = i    }   }() //一个go程读map go func(){for i := 0; i < 10000; i++ { fmt.Println(m[i])    }   }() time.Sleep(time.Second*20)
}

运行报错:

fatal error: concurrent map read and map write

可以使用读写锁(sync.RWMutex)实现互斥访问。

package mainimport ("fmt""time""sync"
)var m = make(map[int]int)
var rwMutex sync.RWMutexfunc main() {//一个go程写map go func(){rwMutex.Lock()for i := 0; i < 10000; i++ {m[i] = i    }   rwMutex.Unlock()}() //一个go程读mapgo func(){rwMutex.RLock()for i := 0; i < 10000; i++ { fmt.Println(m[i])    }   rwMutex.RUnlock()}() time.Sleep(time.Second*20)
}

正常运行输出:

0
1
...
9999

参考文献

[1] 腾讯云+社区.golang新手容易犯的3个错误
[2] CSDN.golang map中结构体元素是无法取地址的
[3] CSDN.golang中map的一些注意事项

Golang map 三板斧第二式:注意事项相关推荐

  1. Golang map 三板斧第三式:实现原理

    文章目录 1.数据结构 1.1 简介 1.2 核心结构 1.3 数据结构图 2.实现机制 2.1 创建 2.2 增加或修改 2.3 删除 2.4 查找 2.5 迭代 2.5.1 hiter 2.5.2 ...

  2. Golang map 三板斧第一式:快速上手

    文章目录 1.简介 2.申明与定义 3.遍历 4.增删改查 参考文献 1.简介 map 是经常被使用的内置 key-value 型容器,是一个同类型元素的无序组,元素通过另一类型唯一键进行索引. 其键 ...

  3. golang map嵌套struct 结构体字段 不能直接修改 解决方法

    目录 错误信息 错误原因 解决方法 错误信息 Reports assignments directly to a struct field of a map 错误原因 结构体作为map的元素时,不能够 ...

  4. golang map 排序

    golang中map元素是随机无序的,所以在对map range遍历的时候也是随机的,不像php中是按顺序.所以如果想按顺序取map中的值,可以采用以下方式: import ("fmt&qu ...

  5. golang map源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 1. map数据结构 Golang的map使用哈希表作为底层实现,一个哈希表里可以有多个哈希表节点,也即bucket,而每个b ...

  6. Golang map 如何进行删除操作?

    map 的删除操作 Golang 内置了哈希表,总体上是使用哈希链表实现的,如果出现哈希冲突,就把冲突的内容都放到一个链表里面. Golang 还内置了delete函数,如果作用于哈希表,就是把 ma ...

  7. golang map合并_Golang之流式编程

    流处理(Stream processing)是一种计算机编程范式,其允许给定一个数据序列(流处理数据源),一系列数据操作(函数)被应用到流中的每个元素.同时流处理工具可以显著提高程序员的开发效率,允许 ...

  8. Golang map的底层实现

    转自https://blog.csdn.net/i6448038/article/details/82057424并修改 map是Go语言中基础的数据结构,在日常的使用中经常被用到.但是它底层是如何实 ...

  9. Golang map源码详解

    Golang的map是用哈希表实现的,在实现性能上非常优秀,这里会主要对map创建.插入.查询.删除以及删除全部的源码做详解,刻意避开了扩容以及迭代相关的代码,后续会用一个新的文章去讲述.Golang ...

最新文章

  1. 科大星云诗社动态20210323
  2. 转载 - 整数划分问题
  3. linux--GCC简单用法
  4. Java获取硬盘信息
  5. 简记Ubuntu下载 Android源码
  6. erlang学习笔记3 gen_event
  7. 极速版RPS选股,一秒出结果的方案是如何实现的!股票量化分析工具QTYX-V2.5.3...
  8. 数据库全栈工程师(DevDBOps)低首付、高回报,先就业后付款
  9. Tautology (logic)介绍
  10. 2017滴滴校招 数字和为sum的方法数(DP)
  11. excel2010将数字变成以文本存储的数字
  12. 浅谈知识付费模式的兴起及意义
  13. can收发器 rx_CANOpen系列教程03_CAN收发器功能、原理及作用
  14. DNS概述和DNS服务器部署(详细正向解析)
  15. 学生学分信息管理系统-C语言
  16. php切换背景颜色,点击切换背景颜色
  17. 通过mac地址查询ip
  18. Flarum 宝塔完美迁移教程
  19. Errors during downloading metadata for repository ‘AppStream 报错
  20. 下载csdn资源但点击下载按钮没反应

热门文章

  1. Linux ioctl, fcntl
  2. 【Android】不依赖焦点和选中的TextView跑马灯
  3. read实现交互输入自动化(笔记)
  4. STL之multiset简介
  5. IIS6上配置CGI有两个要点(转)
  6. error: storage class specified for parameter问题-转
  7. Linux查看文件内容的几种方式
  8. L1-042 日期格式化-PAT团体程序设计天梯赛GPLT
  9. stream去重_List stream 对象 属性去重
  10. ps_基础技术提升topic基础知识调研