问题描述 :golang 中对 map 类型中的 struct 赋值报错

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。