一文掌握Go语言的map(底层原理+使用方法)

map

map是一种无序的基于key-value的数据结构

哈希表

提到key-value就会想到哈希表,哈希表通常会有一堆桶来存储键值对,一个键值对来了,会选择一个桶。

有两种方法比较常用:

1.取模法 hash%m 用hash值与桶的个数m取模

2.与运算 hash&(m-1) 哈希值与m-1进行与运算,若想运算结果在区间内,并且不会出现空桶则m必须是2的整数次幂

如果之后还有键值对选择这个桶,就是发生了哈希冲突,解决冲突的办法,常用的有两种:

1.开放地址法,找到被占用的桶后面没被占用的桶来用。

2.拉链法, 在被占用的桶后面链一个新桶存储这个键值对。

哈希冲突的发生会使哈希表的读写效率降低,选择散列均匀的哈希函数可以减少哈希冲突的发生,适时对哈希表进行扩容也是保障读写效率的有效手段,通常会把存储键值对的数目与桶的数目的比值作为是否需要扩容的判断依据,这个比值被称为负载因子:Load Factor = count/m

分配新桶时,需要把旧桶里存储的键值对都迁移到新桶中,如果存储的键值对信息较多,一次性迁移所有桶花费的时间就比较明显著,所以通常会在哈希表扩容时,先分配足够多的新桶,增加一个字段记录旧桶的位置,再增加一个字段记录旧桶迁移的进度,在哈希表每次读写操作的时候,如果检测到当前正处于扩容阶段,就完成一部分键值迁移任务,直到所有迁移完成,旧桶不再使用,才算真正完成一次哈希表的扩容,这就是渐进式扩容,可以避免一次性扩容带来的性能瞬时抖动。

Go语言中的map

GO语言中Map类型的底层实现就是哈希表

type hmap struct{
   
    count      int					//键值对数目
    flags      uint8				
    B          uint8				//桶的数目2^B个
    noverflow  uint16				//使用溢出桶的数量
    hash0	   uint32
    
    buckets    unsafe.Pointer		//桶
    oldbuckets unsafe.Pointer		//旧桶
    nevacuate  uintptr				//即将迁移的旧桶编号
    
    extra      *mapextra			//记录溢出桶的相关信息
}

bmap结构

在这里插入图片描述

溢出桶内存布局与常规桶相同,是为了减少扩容次数而引进的,实际上,如果哈希表要分配的桶的数目大于2^4
就认为使用到溢出桶的几率较大,就会预分配2^(B-4)个溢出桶备用,这些溢出桶与常规桶在内存中是连续的。

map扩容规则

当负载因子超过6.5就会触发翻倍扩容,如果负载因子没超标而使用的溢出桶较多则进行等量扩容,当B<=15 noverflow >= 2^B 或者 B > 15 noverflow > 2^15 则溢出桶就算较多了,等量扩容就是创建和旧桶一样多的新桶,然后把键值对迁移过来,这样做的原因是,当桶的负载因子没有超过上限值,却使用了很多溢出桶,只可能是很多键值对被删除的情况。

在这里插入图片描述

这样迁移到新桶中,能够排列的更加紧凑,从而减少溢出桶的使用。

在这里插入图片描述

map定义

Go语言中 map的定义语法如下:

map[KeyType]ValueType

其中,

  • KeyType:表示键的类型。
  • ValueType:表示键对应的值的类型。

map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:

make(map[KeyType]ValueType, [cap])

其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。

map基本使用

map中的数据都是成对出现的,map的基本使用示例代码如下:


                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值