解决哈希冲突的方法
Go 的 map 主要采用 链地址法(Separate Chaining) 来解决哈希冲突。
与传统的单向链表(每个节点只存一个元素)不同,Go 对此进行了内存和性能上的优化:
- 桶(Bucket)机制:哈希表的每个节点是一个桶(即底层结构
bmap)。 - 批量存储:每个桶内部可以连续存储最多 8 个键值对(K-V)。
- 桶内遍历:当发生哈希冲突,多个 key 被映射到同一个桶时,Go 会遍历这 8 个槽位,寻找空闲位置插入。这种设计能更好地利用 CPU 缓存(局部性原理),减少内存分配的开销。
溢出桶(Overflow Bucket)
作用: 当一个常规桶(Normal Bucket)的 8 个槽位都已经存满,此时若还有新的 key 发生哈希冲突并映射到该桶,就需要额外的空间来容纳这些“溢出”的元素。溢出桶就是用来解决单个桶容量上限问题的。
连接方式:
- 链表结构:在编译期生成的
bmap底层结构中,末尾会包含一个overflow指针。 - 动态挂载:当常规桶满时,Go 运行时会分配一个新的溢出桶,并将当前常规桶(或上一个溢出桶)的
overflow指针指向这个新分配的溢出桶。 - 形成链条:如果溢出桶也满了,就会继续追加新的溢出桶,最终形成一个类似
常规桶 -> 溢出桶1 -> 溢出桶2 -> ...的单向链表结构。
补充:如果溢出桶的数量过多,会导致链表过长,查找性能退化为 。为了防止性能严重下降,此时 Go 的 map 会触发等量扩容(SameSizeGrow),通过重新整理内存、紧凑数据来减少溢出桶的数量,从而恢复访问效率。