前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang map 三板斧第二式:注意事项

Golang map 三板斧第二式:注意事项

作者头像
恋喵大鲤鱼
发布2020-11-12 12:02:04
1.1K0
发布2020-11-12 12:02:04
举报
文章被收录于专栏:C/C++基础C/C++基础

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

1.map 默认初始值为 nil

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

代码语言:javascript
复制
var mapName map[string]string

// 使用 comma-ok 式
if v, ok := mapName["dable"]; ok {
	name = v
}

// 直接取(不关心是否存在)
name = mapName["dable"]

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

代码语言:javascript
复制
var m map[string]string
m["dable"] = "male"

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

2.map range 顺序的随机性

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

参考如下程序:

代码语言:javascript
复制
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)
	}
}

运行输出的结果可能是:

代码语言:javascript
复制
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。

代码语言:javascript
复制
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.map 值传递表现出引用传递的效果

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

考察如下程序:

代码语言:javascript
复制
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)
		}
	}
}

运行输出:

代码语言:javascript
复制
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 中的元素并不是一个变量,而是一个值。因此,我们不能对 map 的元素进行取址操作。

代码语言:javascript
复制
var m = map[int]int {
	0 : 0,
	1: 1,
}

func main() {
	fmt.Println(&m[0])
}

运行报错:

代码语言:javascript
复制
cannot take the address of m[0]

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

代码语言:javascript
复制
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)
        }    
    }   
}

编译报错:

代码语言:javascript
复制
cannot assign to struct field personMap[name].isDead in map

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

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

代码语言:javascript
复制
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)
                }    
        }   
}

输出结果:

代码语言:javascript
复制
px is dead

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

代码语言:javascript
复制
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)
                }    
        }   
}

输出结果:

代码语言:javascript
复制
px is dead

5.map 并发读写问题

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

代码语言:javascript
复制
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)
}

运行报错:

代码语言:javascript
复制
fatal error: concurrent map read and map write

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

代码语言:javascript
复制
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)
}

正常运行输出:

代码语言:javascript
复制
0
1
...
9999

参考文献

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-11-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.map 默认初始值为 nil
  • 2.map range 顺序的随机性
  • 3.map 值传递表现出引用传递的效果
    • 4.map 元素不可取址
    • 5.map 并发读写问题
    • 参考文献
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档