最全Golang 中的 map 详解_golang map(3),2024年最新腾讯架构师首发

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

+ - [1、开放寻址法](#1_143)
	- [2、链地址法](#2_147)
	- [3、两种方案的比较](#3_151)

Golang 中的 map 详解

一、什么是 map?

1、map 的定义

在计算机科学里,被称为相关数组、map、符号表或者字典,是由一组 <key, value> 对组成的抽象数据结构,并且同一个 key 只会出现一次。

两个关键点:map 是由 key-value 对组成的;key 只会出现一次。

map 的设计也被称为 “The dictionary problem(字典问题)”,它的任务是设计一种数据结构用来维护一个集合的数据,并且可以同时对集合进行增删查改的操作。

2、map 的数据结构

最主要的数据结构有两种:哈希查找表(Hash table)、搜索树(Search tree)。

  • 哈希查找表(Hash table)
      哈希查找表使用哈希函数将 key 分配到不同的桶(bucket,也就是数组的不同 index),开销主要在哈希函数的计算以及数组的常数访问时间,在很多场景下,哈希查找表的性能很高。
  • 搜索树(Search tree)
      搜索树一般采用自平衡搜索树,包括:AVL 树,红黑树。

哈希查找表的平均查找效率是 O(1),最差是 O(N),如果哈希函数设计的很好,最坏的情况基本不会出现。自平衡搜索树法的最差搜索效率是 O(logN)。遍历自平衡搜索树,返回的 key 序列,一般会按照从小到大的顺序;而哈希查找表则是乱序的。

二、Golang 中 map 的类型

Golang 中 map 是一个指针,占用 8 个字节。当使用 make 创建 map 时,底层调用的是 makemap() 函数,makemap() 函数返回的是一个指针,因为返回的是指针,所以 map 作为参数的时候,函数内部能修改map。

func makemap(t \*maptype, hint int, h \*hmap) \*hmap {}

三、map 的底层实现

源码位于 src\runtime\map.go 中。

golang 中 map 底层使用的是哈希查找表,用链表来解决哈希冲突。每个 map 的底层结构是 hmap,是由若干个结构为 bmap 的 bucket 组成的数组,每个 bucket 底层都采用链表结构。

hmap 的结构:

type hmap struct {
	count      int            // map中元素的数量,调用len()直接返回此值
	flags      uint8          // 状态标识符,key和value是否包指针、是否正在扩容、是否已经被迭代
	B          uint8          // map中桶数组的数量,桶数组的长度的对数,len(buckets) == 2^B,可以最多容纳 6.5 \* 2 ^ B 个元素,6.5为装载因子
	noverflow  uint16         // 溢出桶的大概数量,当B小于16时是准确值,大于等于16时是大概的值
	hash0      uint32         // 哈希种子,用于计算哈希值,为哈希函数的结果引入一定的随机性
	buckets    unsafe.Pointer // 指向桶数组的指针,长度为 2^B ,如果元素个数为0,就为 nil
	oldbuckets unsafe.Pointer // 指向一个旧桶数组,用于扩容,它的长度是当前桶数组的一半
	nevacuate  uintptr        // 搬迁进度,小于此地址的桶数组迁移完成
	extra      \*mapextra      // 可选字段,用于gc,指向所有的溢出桶,避免gc时扫描整个map,仅扫描所有溢出桶就足够了
}

// 溢出桶结构
type mapextra struct {
	overflow    \*[]\*bmap // 指针数组,指向所有溢出桶
	oldoverflow \*[]\*bmap // 指针数组,发生扩容时,指向所有旧的溢出桶
	nextOverflow \*bmap // 指向所有溢出桶中下一个可以使用的溢出桶
}

bmap的结构:

type bmap struct {
    tophash [bucketCnt]uint8    // bucketCnt=8,// 存放key哈希值的高8位,用于决定kv键值对放在桶内的哪个位置
}

//实际上编辑期间会动态生成一个新的结构体
type bmap struct {
	topbits  [8]uint8     // 存放key哈希值的高8位,用于决定kv键值对放在桶内的哪个位置
	keys     [8]keytype   // 存放key的数组
	values   [8]valuetype // 存放value的数组
	pad      uintptr      // 用于对齐内存
	overflow uintptr      // 指向下一个桶,即溢出桶,拉链法
}

buckets是一个bmap数组,数组的长度就是 2^B。每个bucket固定包含8个key和value,实现上面是一个固定的大小连续内存块,分成四部分:tophash 值,8个key值,8个value值,指向下个bucket的指针。

tophash 值用于快速查找key是否在该bucket中,当插入和查询运行时都会使用哈希哈数对key做哈希运算,获取一个hashcode,取高8位存放在bmap tophash字段中。

桶里面会最多装 8 个 key,这些 key 之所以会落入同一个桶,是因为它们经过哈希计算后,哈希结果是“一类”的。在桶内,又会根据 key 计算出来的 hash 值的高 8 位来决定 key 到底落入桶内的哪个位置(一个桶内最多有8个位置)。

如图,B=5 表示hmap的有2^5=32个bmap,buckets是一个bmap数组,其长度为32,每个bmap有8个key。

在这里插入图片描述

桶结构的很多字段得在编译时才会动态生成,比如key和values等

桶结构中,之所以所有的key放一起,所有的value放一起,而不是key/value一对对的一起存放,目的便是在某些情况下可以省去pad字段,节省内存空间。由于内存对齐的原因,key0/value0/key1/value1… 这样的形式可能需要更多的补齐空间,比如 map[int64]int8 ,1字节的value后面需要补齐7个字节才能保证下一个key是 int64 对齐的。

golang中的map使用的内存是不会收缩的,只会越用越多。

每个 bucket 设计成最多只能放 8 个 key-value 对,如果有第 9 个 key-value 落入当前的 bucket,那就需要再构建一个溢出桶 bucket ,通过 overflow 指针连接起来。

四、map 的扩容

1、装载因子(平均每个桶存储的元素个数)

Go的装载因子阈值常量:6.5,map 最多可容纳 6.5*2^B 个元素。

装载因子等于 map中元素的个数 / map的容量,即len(map) / 2^B。装载因子用来表示空闲位置的情况,装载因子越大,表明空闲位置越少,冲突也越多。随着装载因子的增大,哈希表线性探测的平均用时就会增加,这会影响哈希表的性能,当装载因子大于70%,哈希表的性能就会急剧下降,当装载因子达到100%,整个哈希表就会完全失效,这个时候,查找和插入任意元素的复杂度都是O(N),因为需要遍历所有元素。

另外装载因子与扩容、迁移等重新散列(rehash) 行为有直接关系:

  • 在程序运行时,会不断地进行插入、删除等,会导致 bucket 不均,内存利用率低,需要迁移。
  • 在程序运行时,出现装载因子过大,需要做扩容,解决 bucket 过大的问题。

为什么装载因子是6.5?不是8?不是1?
  装载因子是哈希表中的一个重要指标,主要目的是为了平衡 buckets 的存储空间大小和查找元素时的性能高低。实际上这是 Go 官方的经过认真的测试得出的数字,一起来看看官方的这份测试报告。包含四个指标:
在这里插入图片描述

  • loadFactor:负载因子,也叫装载因子;
  • %overflow:溢出率,有溢出 bukcet 的百分比;
  • bytes/entry:平均每对 key/alue 的开销字节数;
  • hitprobe:查找一个存在的 key 时,要查找的平均个数;

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!**

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 26
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值