go语言结构转map go map转string( 四 )


如果未迁移完毕,赋值/删除的时候,扩容完毕后(预分配内存),不会马上就进行迁移 。而是采取 增量扩容 的方式,当有访问到具体 bukcet 时,才会逐渐的进行迁移(将 oldbucket 迁移到 bucket)
nevacuate 标识的是当前的进度 , 如果都搬迁完,应该和2^B的长度是一样的
在evacuate 方法实现是把这个位置对应的bucket,以及其冲突链上的数据都转移到新的buckets上 。
转移的判断直接通过tophash 就可以,判断tophash中第一个hash值即可
遍历的过程,就是按顺序遍历 bucket,同时按顺序遍历 bucket 中的 key 。
map遍历是无序的 , 如果想实现有序遍历,可以先对key进行排序
为什么遍历 map 是无序的?
如果发生过迁移,key 的位置发生了重大的变化,有些 key 飞上高枝,有些 key 则原地不动 。这样 , 遍历 map 的结果就不可能按原来的顺序了 。
如果就一个写死的 map,不会向 map 进行插入删除的操作,按理说每次遍历这样的 map 都会返回一个固定顺序的 key/value 序列吧 。但是 Go 杜绝了这种做法,因为这样会给新手程序员带来误解,以为这是一定会发生的事情,在某些情况下,可能会酿成大错 。
Go 做得更绝,当我们在遍历 map 时 , 并不是固定地从 0 号 bucket 开始遍历,每次都是从一个**随机值序号的 bucket开始遍历,并且是从这个 bucket 的一个 随机序号的 cell **开始遍历 。这样,即使你是一个写死的 map,仅仅只是遍历它,也不太可能会返回一个固定序列的 key/value 对了 。
golang map源码浅析 golang 中 map的实现结构为: 哈希表 + 链表 。其中链表,作用是当发生hash冲突时,拉链法生成的结点 。
可以看到,[]bmap是一个hash table,每一个 bmap是我们常说的“桶” 。经过hash 函数计算出来相同的hash值,放到相同的桶中 。一个 bmap中可以存放 8个 元素, 如果多出8个,则生成新的结点,尾接到队尾 。
以上是只是静态文件 src/runtime/map.go 中的定义 。实际上编译期间会给它加料 ,动态地创建一个新的结构:
上图就是 bmap的内存模型,HOB Hash指的就是 top hash 。注意到 key 和 value 是各自放在一起的,并不是key/value/key/value/...这样的形式 。源码里说明这样的好处是在某些情况下可以省略掉 padding 字段,节省内存空间 。
每个 bmap设计成 最多只能放 8 个 key-value 对,如果有第 9 个 key-value 落入当前的 bmap,那就需要再构建一个 bmap,通过overflow指针连接起来 。
map创建方法:
我们实际上是通过调用的makemap,来创建map的 。实际工作只是初始化了hmap中的各种字段,如:设置B的大?。?设置hash 种子 hash 0.
注意 :
makemap返回是*hmap指针,即map 是引用对象,对map的操作会影响到结构体内部。
使用方式
对应的是下面两种方法
map的key的类型,实现了自己的hash 方式 。每种类型实现hash函数方式不一样 。
key 经过哈希计算后得到hash值 , 共 64 个 bit 位 。其中后B 个bit位置,用来定位当前元素落在哪一个桶里 ,  高8个bit 为当前 hash 值的top hash 。实际上定位key的过程是一个双重循环的过程,外层循环遍历 所有的overflow,内层循环遍历 当前bmap 中的 8个元素。
举例说明: 如果当前 B 的值为 5,那么buckets 的长度 为 2^5 = 32 。假设有个key 经过hash函数计算后 , 得到的hash结果为:
外层遍历bucket 中的链表
内层循环遍历 bmap中的8个 cell
建议先不看此部分内容,看完后续修改 map中元素 - 扩容操作后 再回头看此部分内容 。
扩容前的数据:

推荐阅读