在写项目过程中,遇到一个问题,即go
的map
如果value
是结构体时,无法对结构体进行局部更新
即:1
2
3
4
5
6
7
8
9type demo struct {
a int
b int
}
func main() {
var m = make(map[string]demo)
m["a"] = demo{1,2}
m["a"].a = 3
}
此代码无法通过编译,报错cannot assign to struct field m["a"].a in map
。
但是对m["a"]
进行一整个结构体的赋值则是允许的,在java
中是可以局部赋值的,但是go
中却不允许,很奇怪,于是查找解决方案。
map底层结构
首先讲map
的底层结构,不同于cpp
,go
的map
底层是hash
表,初始化map
时,就初始化了一个hmap
,作为该map
的头1
2
3
4
5
6
7
8
9
10
11
12type hmap struct {
count int
flags uint8
B uint8
noverflow uint16
hash0 uint32
buckets unsafe.Pointer
oldbuckets unsafe.Pointer
nevacuate uintptr
extra *mapextra
}
以上是hmap
的结构体定义,关于各个值代表的含义网上有很多介绍,这里就不赘述。
hmap
不存储具体的key
和value
,key
和value
由bucket
存储,一个bucket
可以存储8个key
和8个value
,其中key
排列在一起,value
排列在一起,这样的好处就是在key
和value
的长度不一致时,可以避免空间浪费。
在map
增加元素的过程中,可能会出现空间不够用的情况,这个时候就会进行扩容,这里比较不同的是,为了防止扩容带来的性能抖动,go
选择了渐进式扩容,也就是在一次删除或者更新操作中不全部扩容完毕,而是进行一部分的扩容,扩容会将空间扩展为之前的两倍。
hmap
的字段中,buckets
就是新桶数组的指针,oldbuckets
就是旧桶数组的指针。底层就是数组+链表。
在hmap
中定义了一个extra
字段,extra
字段也是一个结构体:1
2
3
4
5
6type mapextra struct {
overflow *[]*bmap
oldoverflow *[]*bmap
nextOverflow *bmap
}
其中overflow
是一个切片,在map的设计中,假如分配了2^B - 1个bucket
,也会分配2^B - 1
个overflowBucket
,当bucket
装满时,就链接到一个overflowBucket
进行扩展。当overflow
快存满时,也会进行扩容,这个时候不会引起翻倍扩容,而是等额扩容,因为此时很可能是进行了大量删除工作,相当于给内存做一次整理。
问题原因
前面提到,map
的更新等操作是可能会引起扩容的,所以内存地址会变。
- 通过
m["a"].a = 3
这种操作进行更新,如果不存在m["a"]
值,则会引起插入操作,这时没有一个完整结构体,可能会导致问题,如果存在,扩容时,value
会在内存中移动,旧的指针地址在map
改变时会无效,所以为了安全考虑,go
禁止这样寻址,两个原因,如果对象不存在,则返回零值,零值是不可变对象,所以不能寻址,如果对象存在,因为go
中map
实现中元素的地址是变化的,这意味着寻址的结果是无意义的。 golang
中没有引用类型,通过m["a"].a
取出来的是一份拷贝,修改这里的拷贝,实际修改的内容没有复制回原来的struct
。
解决方案
与java
不同,java
中存在引用类型,所以可以通过那种方式进行修改,就算会扩容,修改引用,原始数据总是跟着变。
解决方案有两种,一是进行全局赋值,例如:1
2
3tmp := m["a"]
tmp.a = 333
m["a"] = tmp
这样可行的原因是相当于给m["a"]
赋新值了,不管m["a"]
存不存在都不会出现问题。
或者将value
改为结构体指针:1
2
3
4var m = make(map[string]*demo)
m["a"] = &demo{1, 2}
m["a"].a = 333
fmt.Println(m["a"])
这样在map
中存储的就是结构体的指针,获取到的拷贝,也是对应地址,所以自然可以修改成功,不管map
再怎么扩容,key
对应的value
总是不变的。
- 本文标题:关于Go的map无法对结构体值局部赋值的探究
- 本文作者:Kale
- 创建时间:2021-04-07 15:42:44
- 本文链接:https://kalew515.com/2021/04/07/关于Go的map无法对结构体值局部赋值的探究/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!