581 字
3 分钟
Go中的Map
2026-02-01

底层原理#

Go Map 的底层实现是一个哈希表,在运行时表现为一个指向 hmap 结构体的指针,hamp 中记录了桶数组指针 buckets、溢出桶指针以及元素个数等字段。每个桶是一个 bmap 结构体,能储存8个键值对和8个 tophash,并有指向下一个溢出桶的指针 overflow。为了内存紧凑,bmap 中采用的是先存8个键再存8个值的存储方式。

Map是否是并发安全的?#

map 不是线程安全的。 在查找、赋值、遍历、删除的过程中都会检测写标志,一旦发现写标志置位(等于1),则直接 panic。赋值和删除函数在检测完写标志是复位之后,先将写标志位置位,才会进行之后的操作。

扩容机制#

向 map 插入新 key 的时候,会进行条件检测,符合下面这2个条件,就会触发扩容。

  1. 装载因子超过阈值,源码里定义的阈值是6.5,这个时候会触发双倍扩容
  2. overflow 的 bucket 数量过多时这两种情况触发等量扩容:
    1. B<15B < 15,也就是 bucket 总数 2B2^B 小于 2152^{15} 时,如果 overflow 的bucket 数量 超过 2B2^B
    2. B>=15B >= 15,也就是 bucket 总数 2B2^B 大于等于 2152^{15} 时,如果 overflow 的bucket 数量 超过 2B2^B

Go 的扩容是渐进式(Gradual)的。它不会在触发扩容时“stop the world”来一次性吧所有数据搬迁岛新空间,而是只分配新空间,然后在后续的每一次插入、修改或删除操作时,才会顺便搬迁一两个旧桶的数据。这种设计将庞大的扩容成本分摊到了多次操作中,极大地减少了服务的瞬间延迟(STW),保证了性能的平滑性。

如果是触发双倍扩容,会新建一个 buckets 的数组,新的 buckets数量大小是原来的两倍,然后旧 buckets 数据搬迁到新的 buckets。如果是等量扩容,buckets 数量维持不变,重新做一遍类似双倍扩容的搬迁动作,把松散的键值对重新排列一次,使得同一个 buckets 中的 key 排列地更紧密,这样节省空间,存取效率更高。

Go中的Map
https://blog.sleepwf.dev/posts/go中的map/
作者
Sleepwf
发布于
2026-02-01
许可协议
CC BY-NC-SA 4.0