Golang map实现原理及源码分析

本文深入解析Golang中的map实现原理,包括基本结构(哈希表、hmap和bmap)、负载因子(6.5的设定原因)、扩容机制、遍历的随机性以及并发使用时的sync.map,探讨了如何在并发环境下保证数据安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文涉及到的源码版本为Go SDK 1.16.1 

1、map的基本结构

        map是Golang中的一种常用数据结构,其本质上是一种哈希表,类似于 java 的 HashMap以及Python的字典(dict),是一种存储键值对(Key-Value)的数据结构。一般的Map会包含两个主要结构:

  • 数组:数组里的值指向一个链表
  • 链表:目的解决hash冲突的问题,并存放键值

        而在Golang中,解决hash冲突的不是链表,而是数组(既内存中的连续空间),而且使用了两个数组分别存放键和值,如下图所示。其中提到了map中的两个核心的结构体:hmap和bmap,每个map的底层数据结构是hmap,而每个hamp是有若干个bmap组成的bucker数组hmap的结构体定义如下(src/runtime/map.go): 

type hmap struct {
	count     int //元素个数
	flags     uint8
	B         uint8  // 扩容相关字段, B是buckets数组的长度的对数2^B
	noverflow uint16 // 溢出的bucket个数,一个bmap中最多存8组键值对,存满了就往指向的这个bmap里存
	hash0     uint32 // hash因子

	buckets    unsafe.Pointer // buckets 数组指针
	oldbuckets unsafe.Pointer // 扩容时,存放之前的buckets(Map扩容相关字段)
	nevacuate  uintptr        // 分流次数,成倍扩容分流操作计数的字段(Map扩容相关字段)

	extra *mapextra // 用于扩容的指针
}

type mapextra struct {
    overflow    *[]*bmap
    oldoverflow *[]*bmap
    nextOverflow *bmap
}

  而bmap 就是我们常说的“桶”,桶里面会最多装 8 个 key,这些 key 之所以会落入同一个桶,是因为它们经过哈希计算后,哈希结果是相同的。哈希函数相同的情况下又会根据 key 计算出来的 hash 值的高 8 位来决定 key 到底落入桶内的哪个位置(一个桶内最多有8个位置)。 bmap在map.go中的代码定义如下,值得一提的的是,在编译时会动态创建一个新的bmap结构:

// A bucket for a Go map.
type bmap struct {
	tophash [bucketCnt]uint8 //长度为8的数组
}

//编译时创建的新bamp结构
type bmap struct {
  topbits  [8]uint8 //长度为8的数组,[]uint8,存储的是key获取的hash的高8位,遍历时对比使用,提高性能。
  keys     [8]keytype //长度为8的数组,存储具体的key值。
  values   [8]valuetype //长度为8的数组,存储具体的value值
  pad      uintptr //指向的hmap.extra.overflow溢出桶里的bmap,上面的字段topbits、keys、elems长度为8,最多存8组键值对,存满了就往指向的这个bmap里存
  overflow uintptr //对齐内存使用的,不是每个bmap都有会这个字段,需要满足一定条件
}

       在了解了hmapbmap的基本结构后,就可以就得到如下的Map结构图:

  

 2、负载因子

        负载因子是衡量Hash表中空间利用率的指标,负载因子= hash表中已存储的键值对的总数量/hash桶的个数(即hmap结构中buckets数组的个数)。我们知道负载因子决定了Hash表的存储效率:负载因子过小会导致空间利用率较低;而负载因子较大则会导致Hash表的插入和查找频繁发生碰撞,导致Hash表退化成链表,触发扩容。在Golang中负载因子被设定为6.5,为什么设置为6.5而不是其他的数值呢,Golang官方给出了一组测试数据:

        从表中我们能够看到当负载因子设置为6.5时,溢出率(%overflow),每个键值对占用字节比(bytes/entry)和查找次数取得一定平衡 。当在Golang中调用make(map[k]v,hint)初始化map时,会根据初始化元素个数以及负载因子来决定创建的buckets个数,核心代码如下所示:

func makemap(t *maptype, hint int, h *hmap) *hmap {
	mem, overflow
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值