链表法
如果key映射到了相同的位置,然后就把这些key 和 value存在一张链表里 golang的header存储了map有关的元数据
- len 当前pma中键值对的大小
- bucket array 存储数据的bucket数组,每个bucket本身可以存储多个键值对,而不是指向一个键值对的链表
- hash seed 用于哈希计算的的种子,用于分散数据并提高安全性 通常一个bucket可以存储8个键值对,这些键值对是根据键的哈希值分配到对应的bucket,
- overflow bucket 当一个bucket的数据数量超过bucket的容量时,会创建overflow bucket来存储多余的数据,这样可以避免直接扩展bucket数组,节省内存空间。u但如果出现过多的overflow bucket,寻找key时就需要遍历所有的overflow bucket,这就会导致性能下降
- 扩容 当map中出现过多overflow bucket而导致性能下降的时候,就需要对map bucket数据进行扩容,以保证读写的性能,是否扩容由load factor的参数控制,load factor是元素数量和bucket数量的比值,比值越高,map的性能越差。目前map采用了一个经验值来确定是否扩容,即load factor=6.5,当load factor超过这个值时,就会触发扩容,每次扩容都会增大bucket数量,让每个bucket中存放的bucket数量降低下来 扩容需要对存量element做rehash,在元素数量较多的情况下,go map采用了增量扩容的方式来i进行扩容来避免一次性的扩容对map造成操作延迟突增,即在访问和插入数据时,对元素一点一点的进行搬迁,直到所有元素完成搬迁 桶扩容后就不会再缩容了
Go1.24新Map实现
在Go 1.24中,map使用swiss table来实现 与Old Map不同,Swiss Table 使用开放寻址法,
a swiss table = N* (metadata array + slots array)
swiss table将桶(slot)分为N个group,每个group有16个Slot,即通过开放寻址法中的probing放到一个16个slot的group进行,这样的好处是可以通过一个SIMD指令并行探测16个slot,这种方法被称为group probing

一个Group由metadata和16个slots组成。metadaa里存储的是元数据,slot存储的是key和value
当向swiss table插入i一个元素时或者查找时,swiss table会通过hash函数对key 进行求值,结果是一个8字节(64bit)的数。
哈希值的高57位称为H1,低7位称为H2,前者H1用于标示该元素在group内的索引,查找和插入都需要它,H2将被用于该元素的元数据,放在metadata中存储,用于快速的group probing。
每个Group的metadata也是一个16字节数组,每个字节对应一个slot,是该slot的控制字节。这个字节的8bit位组成如下
metadata中的控制字节状态有三个状态
- 最高位为1,其他为0,说明slot未被占用
- 最高位为0,后7位为key哈希后的后7bit,也就是H2,说明已经被element使用
- 最高位为1,后七位为1111110,说明对应的slot为已删除状态,后续可以被继续使用
上图为key的hash值H2指纹o通过SIMD指令从16个位置快速得到匹配pos的过程。可以看到匹配的过程能快速排除不可能的匹配项,减少了不必要的内存访问。
swiss table的16个条目也不是随意选择的,而是基于SSE2寄存器长度(128bit,16bytes)和现代CPU缓存的行大小(64字节)优化的,保证了一个Group的控制字节能被单次SIMD指令处理
swiss table也是通过load factor来判定是否需要对哈希表进行扩容,一旦扩容,swisstable通常会将group数量增加一倍,然后i重新计算当前所有元素的在新groups中的新位置,当然这个也是有一定开销的。
如果元素插入时,当前group已满,就必须探测到下一个Group,并将元素插入后下一个group。在该元素查找操作中,probing也会跨group进行
Go中的实现
GO引入了多table的设计,以支持渐进式扩容。也就是说一个map实际上是多个swiss table,每个tableu都有自已独立的load factor,这样就可以将o扩容的开销从全部数据变为局部少量数据,减少扩容的影响。 使用可扩展i哈希根据key的高位选择table。 为了实现渐进式扩容,数据分散在多个table里,单个table中容量有上限,超过上限时分裂成两个table。 Go使用Dict管理多个Table,Directory是Table的数组,大小为2^globalDepth。如果globalDepth=2,那么Directory最多有4个table,分别为0x00,0x01,0x10,0x11。Go通过key的hash值的前globalDepth个bit来选择table。这是一个可扩展hash,动态哈希技术,核心特点是o通过o动态调整使用的哈希位数来实现渐进式的扩容。