1. map
的底层结构
Go 中的 map
是基于哈希表实现的,其底层由两个核心结构体组成:hmap
和 bmap
。
-
hmap
:是map
的头部结构,包含以下字段:-
count
:当前map
中的键值对数量。 -
B
:桶的数量指数,2^B
是当前桶的数量。 -
buckets
:指向当前桶数组的指针。 -
oldbuckets
:扩容时,指向旧桶数组的指针。 -
nevacuate
:记录扩容进度。 -
extra
:存储额外信息(如溢出桶等)。
-
-
bmap
:是存储键值对的桶结构,每个桶最多存储 8 个键值对。桶内通过哈希值的高位和低位来定位键值对。
2. 扩容机制
当 map
中的元素数量增加时,为了维持高效的存取性能,map
会自动触发扩容操作。扩容机制的核心步骤如下:
2.1 触发扩容的条件
-
负载因子超过阈值:负载因子是指
map
中元素数量与桶数量的比值。当负载因子超过 6.5 时,会触发扩容。 -
溢出桶过多:当某个桶的溢出链表过长时,也会触发扩容。
2.2 扩容过程
-
创建新桶数组:新桶数组的大小通常是旧桶数组的两倍。
-
渐进式迁移:Go 采用渐进式扩容策略,即每次插入或删除操作时,会迁移部分桶中的数据。这种策略可以避免一次性迁移所有数据导致的性能抖动。
-
重新哈希:将旧桶中的键值对重新计算哈希值,并迁移到新桶中。
2.3 扩容的细节
-
扩容时,旧桶中的键值对会根据新的哈希值重新分配到新桶中。
-
如果某个键值对被删除,其对应的桶不会立即清理,而是标记为
emptyOne
,以避免在扩容时重新插入。 -
每次插入或删除操作时,都会调用
growWork
函数,逐步完成扩容。
3. 扩容的优化
-
渐进式扩容:避免一次性迁移所有数据,减少性能抖动。
-
负载因子:通过合理的负载因子(如 6.5),在性能和空间利用率之间取得平衡。
4. 注意事项
-
线程安全:Go 的
map
不是线程安全的。在并发环境中,需要通过锁(如sync.Mutex
)来保证并发安全。 -
内存布局:由于
map
可能会扩容,其键值对的内存地址可能会发生变化。