Go中的map是一个指针,占用8个字节,指向hmap结构体
源码中src/runtime/map.go定义了Hmap的数据结构
-
hmap结构体
type hmap struct { count int flags uint8 B uint8 noverflow uint16 hash0 uint32 buckets unsafe.Pointer oldbuckets unsafe.Pointer nevacuate uintptr extra *mapextra }
- 字段包括:
count
:表示当前map中元素的个数。B
:表示buckets数组的长度是2的B次方个。noverflow
:表示溢出桶的数量。buckets
:指向一个数组,数组的每个元素都是一个bucket(桶)。oldbuckets
:在发生扩容时,记录扩容前的buckets数组指针。extra
:一个指向mapextra
结构体的指针,用于保存溢出桶的地址等信息。
- 字段包括:
-
bucket(桶)
- buckets数组中的每个元素都是一个bucket,它实际上是一个指向
bmap
结构体的指针。 - 每个
bmap
结构体存储8个键值对。
- buckets数组中的每个元素都是一个bucket,它实际上是一个指向
-
bmap结构体
- 这是实际存储键值对的数据结构。
- 字段包括:
tophash
:一个长度为8的数组,存储哈希值的高8位,用于快速比较和查找。如果Key所在的tophash值在tophash中,则代表了Key在这个桶中。keys
:一个数组,用于存储键。values
:一个数组,用于存储值。overflow
:当bucket中的键值对超过8个时,使用此指针指向下一个bmap
,形成链表结构来处理溢出。
插入/删除/查找
插入
插入一个新的键值对时,
map
首先会检查当前的负载因子。如果负载因子超过了阈值,map
会先进行扩容操作。然后,map
会计算键的哈希值,并使用这个哈希值来决定应该将键值对存储在哪个桶中。具体的策略是,根据计算得到的哈希值的低B位,决定在buckets数组中的哪个bmap中,然后根据它哈希值的高8位,决定它在桶中的哪个位置(一共8个位置)。删除
删除一个键时,
map
会计算键的哈希值,并使用这个哈希值来定位桶。然后,map
会在这个桶中查找并删除键值对。查找一个键时,
map
会计算键的哈希值,并使用这个哈希值来定位桶。然后,map
会在这个桶中查找键值对。溢出处理
- 当一个bucket中的键值对数量超过8个时,Go语言会使用
overflow
指针将新的键值对存储在下一个bmap
中,形成链表结构。 - 这种链表结构允许map在保持哈希表高效查找特性的同时,也能够动态地处理大量键值对的情况。
扩容操作
- 当map中的元素数量达到某个阈值时(通常是buckets数组长度的某个比例),Go语言会触发扩容操作。
- 扩容操作会创建一个新的、更大的buckets数组,并将旧的键值对重新哈希并分布到新的buckets数组中。
- 为了减少扩容操作对性能的影响,Go语言采用了渐进式扩容的策略,即在扩容过程中仍然允许对map进行读写操作。