Golang map 三板斧第二式:注意事项
文章目录
- 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 三板斧第二式:注意事项相关推荐
- 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 ...
- Golang map 三板斧第一式:快速上手
文章目录 1.简介 2.申明与定义 3.遍历 4.增删改查 参考文献 1.简介 map 是经常被使用的内置 key-value 型容器,是一个同类型元素的无序组,元素通过另一类型唯一键进行索引. 其键 ...
- golang map嵌套struct 结构体字段 不能直接修改 解决方法
目录 错误信息 错误原因 解决方法 错误信息 Reports assignments directly to a struct field of a map 错误原因 结构体作为map的元素时,不能够 ...
- golang map 排序
golang中map元素是随机无序的,所以在对map range遍历的时候也是随机的,不像php中是按顺序.所以如果想按顺序取map中的值,可以采用以下方式: import ("fmt&qu ...
- golang map源码分析
2019独角兽企业重金招聘Python工程师标准>>> 1. map数据结构 Golang的map使用哈希表作为底层实现,一个哈希表里可以有多个哈希表节点,也即bucket,而每个b ...
- Golang map 如何进行删除操作?
map 的删除操作 Golang 内置了哈希表,总体上是使用哈希链表实现的,如果出现哈希冲突,就把冲突的内容都放到一个链表里面. Golang 还内置了delete函数,如果作用于哈希表,就是把 ma ...
- golang map合并_Golang之流式编程
流处理(Stream processing)是一种计算机编程范式,其允许给定一个数据序列(流处理数据源),一系列数据操作(函数)被应用到流中的每个元素.同时流处理工具可以显著提高程序员的开发效率,允许 ...
- Golang map的底层实现
转自https://blog.csdn.net/i6448038/article/details/82057424并修改 map是Go语言中基础的数据结构,在日常的使用中经常被用到.但是它底层是如何实 ...
- Golang map源码详解
Golang的map是用哈希表实现的,在实现性能上非常优秀,这里会主要对map创建.插入.查询.删除以及删除全部的源码做详解,刻意避开了扩容以及迭代相关的代码,后续会用一个新的文章去讲述.Golang ...
最新文章
- 科大星云诗社动态20210323
- 转载 - 整数划分问题
- linux--GCC简单用法
- Java获取硬盘信息
- 简记Ubuntu下载 Android源码
- erlang学习笔记3 gen_event
- 极速版RPS选股,一秒出结果的方案是如何实现的!股票量化分析工具QTYX-V2.5.3...
- 数据库全栈工程师(DevDBOps)低首付、高回报,先就业后付款
- Tautology (logic)介绍
- 2017滴滴校招 数字和为sum的方法数(DP)
- excel2010将数字变成以文本存储的数字
- 浅谈知识付费模式的兴起及意义
- can收发器 rx_CANOpen系列教程03_CAN收发器功能、原理及作用
- DNS概述和DNS服务器部署(详细正向解析)
- 学生学分信息管理系统-C语言
- php切换背景颜色,点击切换背景颜色
- 通过mac地址查询ip
- Flarum 宝塔完美迁移教程
- Errors during downloading metadata for repository ‘AppStream 报错
- 下载csdn资源但点击下载按钮没反应