map 使用起来非常方便,但也有些必须要注意的地方,否则可能会导致程序异常甚至 panic。
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
的运行时错误。
map 在没有被修改的情况下,使用 range 多次遍历 map 时输出的 key 和 value 的顺序可能不同。这是 Go 语言的设计者们有意为之,在每次 range 时的顺序被随机化,旨在提示开发者们,Go 底层实现并不保证 map 遍历顺序稳定,请大家不要依赖 range 遍历结果顺序。Golang 官方博文对此有详细说明:Go maps in action。
参考如下程序:
package main
import (
"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])
}
Golang 中没有引用传递,只有值和指针传递。所以 map 作为函数实参传递时本质上也是值传递,只不过因为 map 底层数据结构是通过指针指向实际的元素存储空间,在被调函数中修改 map,对调用者同样可见,所以 map 作为函数实参传递时表现出了引用传递的效果。因此,传递 map 时,函数形参无需使用指针。
考察如下程序:
package main
import (
"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 变量本身,那么请使用值传递,而不是指针传递,这样会使代码更加简洁可读。
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 main
import (
"fmt"
)
type person struct {
name string
age byte
isDead 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 main
import (
"fmt"
)
type person struct {
name string
age byte
isDead 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 main
import (
"fmt"
)
type person struct {
name string
age byte
isDead bool
}
func whoIsDead(people map[string]person) {
for name, _ := range people {
if people[name].age < 50 {
tmp := people[name]
tmp.isDead = true
people[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
共享 map 在并发读写时需要加锁。先看错误示例:
package main
import (
"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 main
import (
"fmt"
"time"
"sync"
)
var m = make(map[int]int)
var rwMutex sync.RWMutex
func main() {
//一个go程写map
go func(){
rwMutex.Lock()
for i := 0; i < 10000; i++ {
m[i] = i
}
rwMutex.Unlock()
}()
//一个go程读map
go 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的一些注意事项