万字长文了解Go map 常见问题与原理

背景

在开发过程中,很多场景使用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
	}
	
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值