背景
在开发过程中,很多场景使用map可以方便的解决问题,但使用不当时会造成数据错误甚至程序崩溃,本文对map在使用过程中遇到的常见问题进行总结,并解析map与sync.map的底层实现,以便后续更好的使用map
常见问题
nil-map写入时panic
使用var声明一个map时,不会分配地址,这时直接对nil map进行写操作,会直接panic,如下代码
func main() {
var m map[string]string
m["1"] = "1"
fmt.Println(m)
}
运行上面代码时会出现下面错误
panic: assignment to entry in nil map
因此需要使用make方法对其分配内存地址,改成下面的代码即可
func main() {
m := make(map[string]string)
m["1"] = "1"
fmt.Println(m)
}
上面这种情况大多数人在编码时都清楚需要初始化,但下面的例子中,一些开发者在复杂的逻辑中可能会犯错
type Counter struct {
Website string
Start time.Time
PageCounters map[string]int
}
func main() {
var c Counter
c.Website = "baidu.com"
c.PageCounters["/"]++
}
panic: assignment to entry in nil map
对于结构体使用的map也要初始化
直接赋值时引用拷贝
使用“=”号将一个map赋值给另一个map时,是引用拷贝,即浅拷贝,两个map会公用一个地址,以下面代码为例
func main() {
m1 := make(map[string]string)
m1["1"] = "1"
m1["2"] = "2"
// 这里因为后面是直接将m2指向m1的地址,没有对m2进行写入,因此可以不用make初始化
var m2 map[string]string
m2 = m1
fmt.Printf("m1地址%p,m1的值%v\n", m1, m1)
fmt.Printf("m2地址%p,m2的值%v\n", m2, m2)
fmt.Println("------------华丽分割线-------------")
// 改变m1的值
m1["1"] = "3"
fmt.Printf("m1地址%p,m1的值%v\n", m1, m1)
fmt.Printf("m2地址%p,m2的值%v\n", m2, m2)
}
输出如下:
m1地址0xc00007c4b0,m1的值map[1:1 2:2]
m2地址0xc00007c4b0,m2的值map[1:1 2:2]
------------华丽分割线-------------
m1地址0xc00007c4b0,m1的值map[1:3 2:2]
m2地址0xc00007c4b0,m2的值map[1:3 2:2]
从上面可以结果可以看出,直接用“=”号的话只是做浅拷贝,当其中一个变量的值发生改变时,另一个也随之改变,若需要用到深拷贝,使用下面的方式即可
func main() {
m1 := make(map[string]string)
m1["1"] = "1"
m1["2"] = "2"
// 这里因为后面是直接对m2进行写入,因此可以需要make初始化
m2 := make(map[string]string)
for k, v := range m1 {
m2[k] = v
}
fmt.Printf("m1地址%p,m1的值%v\n", m1, m1)
fmt.Printf("m2地址%p,m2的值%v\n", m2, m2)
fmt.Println("------------华丽分割线-------------")
// 改变m1的值
m1["1"] = "3"
fmt.Printf("m1地址%p,m1的值%v\n", m1, m1)
fmt.Printf("m2地址%p,m2的值%v\n", m2, m2)
}
输出
m1地址0xc00007c4b0,m1的值map[1:1 2:2]
m2地址0xc00007c4e0,m2的值map[1:1 2:2]
------------华丽分割线-------------
m1地址0xc00007c4b0,m1的值map[1:3 2:2]
m2地址0xc00007c4e0,m2的值map[1:1 2:2]
从结果可以看出,拷贝后的值虽然一直,但结果却不一样,并且修改m1的值,m2也不会被修改到
无法修改值为结构体的map
若定义的map值是结构体时,不能修改其值,如下面例子
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
m := make(map[int]Student)
m[1] = Student{
Name: "xiaomin",
Age: 1,
}
m[1].Name = "xiaohua"
}
上面例子,如果用Goland编辑代码时,倒数第二行会爆红线提示错误,因为不能直接对map值为结构体进行直接修改,若要修改,有两种方法
第一种是对结构体进行拷贝、修改,再把新的结构体赋值回去,如下所示
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
m := make(map[int]Student)
m[1] = Student{
Name: "xiaomin",
Age: 1,
}
tmp := m[1]
tmp.Name = "xiaohua"
m[1] = tmp
fmt.Println(m)
}
上面代码输出为
map[1:{xiaohua 1}]
第二种方法是将值定义为指针,即通过修改指针的值来达到修改map值的目的,如下面代码
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
m := make(map[int]*Student)
m[1] = &Student{
Name: "xiaomin",
Age: 1,
}
m[1].Name = "xiaohua"
fmt.Printf("m值:%v", *m[1])
}
输出为
m值:{xiaohua 1}
推荐用方法二,方法一需要多声明一个结构体,再进行拷贝,写起来代码会多点并且声明结构体会消耗内存
for range遍历时,指针是不变的
上面说到,解决不能修改结构体时,推荐使用指针的方法,但使用指针时,for range需要注意一点,循环时,指针是不变的,以下面为例
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
sli := []Student{
{
Name: "xiaoming",
Age: 12,
},
{
Name: "xiaohua",
Age: 13,
}}
m := make(map[int]*Student)
for i, v := range sli {
// 打印每一个v
fmt.Println(v)
m[i] = &v
}