问题描述 :golang 中对 map 类型中的 struct 赋值报错
参考:go语言中文文档:www.topgoer.cn
package main
import (
"fmt"
)
func main() {
topgoer := make(map[string]int64, 0)
topgoer["hello"] = 20
fmt.Println(&topgoer["hello"])
}
./main.go:10:14: cannot take the address of topgoer["hello"]
原因是 map 元素是无法取址的,也就说可以得到topgoer[“hello”], 但是无法对其进行修改。
解决办法:使用指针的map
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
type Entity struct {
Value string
}
entityMap := make(map[string]*Entity, 2)
entityMap["cat"] = &Entity{Value: "This is a cat"}
entityMap["cat"].Value = "This is a another cat"
fmt.Println("value ",entityMap["cat"].Value)
}
golang里面的map是通过hashtable来实现的,具体方式就是通过拉链法(数组+链表)来实现的,这里对比c++的map,c++里面的map, 是通过红黑树来实现的。
所以二者在遍历的时候做删除操作,golang的是可以直接操作的,因为内部实现是哈希映射,删除并不影响其他项,而c++中的map删除,由于是红黑树,删除任意一项,都会打乱迭代指针,不能再O(1)时间内删除。
同时,golang里面的key是无序的,即使你顺序添加,遍历的时候也是无序。
golang里面的map,当通过key获取到value时,这个value是不可寻址的,因为map 会进行动态扩容,当进行扩展后,map的value就会进行内存迁移,其地址发生变化,所以无法对这个value进行寻址。也就是造成上述问题的原因所在。map的扩容与slice不同,那么map本身是引用类型,作为形参或返回参数的时候,传递的是值的拷贝,而值是地址,扩容时也不会改变这个地址。而slice的扩容,会导致地址的变化。
MAP的原理
type hmap struct {
count int //元素个数
flags uint8
B uint8 //扩容常量
noverflow uint16 //溢出 bucket 个数
hash0 uint32 //hash 种子
buckets unsafe.Pointer //bucket 数组指针
oldbuckets unsafe.Pointer //扩容时旧的buckets 数组指针
nevacuate uintptr //扩容搬迁进度
extra *mapextra //记录溢出相关
}
type bmap struct {
tophash [bucketCnt]uint8
// Followed by bucketCnt keys
//and then bucketan Cnt values
// Followed by overflow pointer.
}
每个map的底层结构是hmap,是有若干个结构为bmap的bucket组成的数组,每个bucket可以存放若干个元素(通常是8个),那么每个key会根据hash算法归到同一个bucket中,当一个bucket中的元素超过8个的时候,hmap会使用extra中的overflow来扩展存储key。