news 2026/3/4 4:08:17

Go进阶之map

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go进阶之map

1.初始化:

1.1字面量初始化:

func main() { m := map[string]string{ "hello": "world", "hello2": "world2", } for k, v := range m { fmt.Printf("%s-%s\n", k, v) } }

1.2内置函数make初始化:

func main() { m2 := make(map[string]int, 10) m2["hello"] = 1 m2["hello2"] = 2 for k, v := range m2 { fmt.Printf("%s-%d\n", k, v) } }

使用make内置函数初始化map时可以指定容量(也可以不指定).指定容量可以有效的减少分配内存次数.有利于提升性能.

1.3map的增删改查:

func main() { m2 := make(map[string]int, 10) for k, v := range m2 { fmt.Printf("%s-%d\n", k, v) } //添加. m2["hello"] = 1 m2["hello2"] = 2 m2["add"] = 3 //修改. m2["hello"] = 001 //删除 delete(m2, "hello2") //查询 v, exist := m2["add"] if exist { fmt.Println(v) } }

注:

修改操作中.如果键不存在.则map会创建一个新的键值对并存储.等同于添加元素.

删除元素使用内置函数delete()完成.没有返回值.在map为nil或指定键值不存在的话.也不会报错.相当于空操作.

查询操作中.最多可以给两个变量赋值.第一个为值.第二个为bool类型变量.用于表示是否存在指定键.如果不存在.对应的值为相应类型的零值.如果指定一个变量.那么该变量仅表示该键对应的值.如果键不存在.该值同样为相应类型的零值.

内置函数len()可以查询map的长度.该长度为map中键值对的数量.

2.危险操作:

2.1并发读写:

map操作不是原子的.这意味这多协程操作map时有可能产生读写冲突.读写冲突会发生panic从而导致程序退出.

2.2空map:

未初始化的map值为nil.在向值为nil的map添加元素的时候会发生panic.

值为nil的map和长度为空的map长度一致.

func main() { var m1 map[string]int m := make(map[string]int) if len(m1) == len(m) { fmt.Println("长度一致") } }

3.实现原理:

3.1数据结构:

Go语言的map底层使用的是hash实现.一个hash表里可以有多个bucket.而每个bucket保存了map中的一个或一组键值对.

源码位置:runtime/map_noswiss.go:hmap(go版本1.24.0)

type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) clearSeq uint64 extra *mapextra // optional fields }

count 当前保存元素的个数.

B bucket数组大小.

buckets bucket数组.数组长度为2的b次方.

oldbuckets 旧bucket数组.用于扩容.

拥有4个bucket的map.

3.2bucket结构:

type bmap struct { // tophash generally contains the top byte of the hash value // for each key in this bucket. If tophash[0] < minTopHash, // tophash[0] is a bucket evacuation state instead. tophash [abi.OldMapBucketCount]uint8 // Followed by bucketCnt keys and then bucketCnt elems. // NOTE: packing all the keys together and then all the elems together makes the // code a bit more complicated than alternating key/elem/key/elem/... but it allows // us to eliminate padding which would be needed for, e.g., map[int64]int8. // Followed by an overflow pointer. }

tophash 存储hash值得高八位.从下面源码可以看出.值为8.

const ( // Maximum number of key/elem pairs a bucket can hold. OldMapBucketCountBits = 3 // log2 of number of elements in a bucket. OldMapBucketCount = 1 << OldMapBucketCountBits // Maximum key or elem size to keep inline (instead of mallocing per element). // Must fit in a uint8. // Note: fast map functions cannot handle big elems (bigger than MapMaxElemBytes). OldMapMaxKeyBytes = 128 OldMapMaxElemBytes = 128 // Must fit in a uint8. )

tophash 是一个长度为8的整型数组.hash值相同的键.(准确的说是hash值低八位相同的键)存入当前bucket时会将hash值的高八位存储在该数组中.方便后续匹配.

3.3hash冲突:

当有两个或两个以上的key被hash到通一个bucket时.我们称这些键发生了hash冲突.Go使用地址法来解决hash冲突.

一个桶可以放8个键值对.如果超过了8个会重新在创建一个bucket.用类似链表的方式连接起来.

注:hash冲突造成了存取效率低下.

3.4负载因子:

负载因子用于衡量一个hash表冲突情况.公式如下:

负载因子=键数量/bucket数量

示例:

一个bucket数量为4.包含4个key-value的hash表.负载因子为1.

总结:

负载因子过小.说明空间利用率低.

负载因子过小.可能是预分配的空间过大.也可能是大部分元素被删除造成的.

负载因子过大.说明冲突严重.存取效率低.

当hash表中负载因子过大.需要申请更多地bucket.并对所有键值对重新组织.使其均匀的分配到bucket中.这个过程称为rehash.

3.5扩容:

降低负载因子常用的手段就是扩容.为了保证访问效率.当新元素加入的时候会检查是否需要扩容.扩容就是通过空间来换取时间的手段.

3.5.1扩容条件:

负载因子大于6.5.即平均每个桶存储的键值对达到6.5个以上.

overflow的数量达到2的min(15,b).

3.5.2增量扩容:

当负载因子过大时.新建一个bucket数组.新的bucket数组是原来的2倍.然后旧的bucket数组搬迁到新的bucket数组.如果map中存储了数以亿计的键值对.一次性搬迁会造成较大的延时.Go采用了逐步搬迁的策略.即每次访问map的时候都会进行搬迁.每次搬迁两个桶.

负载因子为7/1=7.

扩容的时候先让oldbuckets指向原bucket数组.然后申请新的数组(长度为原来的倍).并将数组指针保存到bucket.等后续迁移完了安全释放oldbuckets.迁移完成如下.

3.5.3等量扩容:

等量扩容并不是扩大容量.bucket数量不变.重新做一遍类似增量扩容的搬迁操作.把松散的键值对重新排列一次.使bucket使用效率更高.保证存取速度.

4增删改查:

无论元素的添加还是查询操作.都需要根据键的hash值确定一个bucket.并查询该bucket中是否存在指定的键.

查询操作:

查到指定的键后获取值就返回.否则返回对应类型的空值.

添加操作:

查到指定的键意味着当前操作实际上是更新操作.否则在bucket中查找一个空余位置插入.

4.1查找过程:

1).根据key值计算hash值.

2).取hash值低位与hmap.B取模来确定bucket的位置.

3).取hash值高位.在tophash数组中查询.

4).如果tophash[i]中存储的hash值与当前key的hash值相等.则获取tophash[i]的key值进行比较.

5).如果当前bucket中没有找到.则依次从溢出的bucket中查找.

6).如果还是找不到不是返回nil.而是返回对应类型的零值.

4.2添加过程:

1).根据key值计算出hash值.

2).取hash值低位与hmp.B取模来确定bucket位置.

3).查找key是否存在.如果存在则直接更新值.

4).如果key不存在,则从该bucket中寻找空余位置并插入.

注:如果当前map处于搬迁过程中.则元素会直接添加到新的bucket数组中.查找过程仍从就数组开始.

4.3删除过程:

删除元素实际上是先查找元素.如果元素存在从相应的bucket中清除.如果不存在则什么也不做.

眼里倒映着转身.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/3 14:38:32

腾讯混元3D 2.0终极指南:零基础实现专业级3D建模

腾讯混元3D 2.0终极指南&#xff1a;零基础实现专业级3D建模 【免费下载链接】Hunyuan3D-2 Hunyuan3D 2.0&#xff1a;高分辨率三维生成系统&#xff0c;支持精准形状建模与生动纹理合成&#xff0c;简化资产再创作流程。 项目地址: https://ai.gitcode.com/tencent_hunyuan/…

作者头像 李华
网站建设 2026/3/3 10:06:31

BlockTheSpot终极指南:免费解锁Spotify高级功能的完整方案

还在为Spotify免费版频繁的广告中断而烦恼吗&#xff1f;BlockTheSpot作为一款专为Windows平台设计的Spotify优化工具&#xff0c;能够帮助你改善音频、视频和横幅广告的体验。本文将为你提供从零基础安装到高级功能配置的完整教程&#xff0c;让你轻松享受更佳的音乐体验。 【…

作者头像 李华
网站建设 2026/3/3 5:40:49

突破性AI图像融合技术:零门槛实现产品场景完美匹配

突破性AI图像融合技术&#xff1a;零门槛实现产品场景完美匹配 【免费下载链接】Fusion_lora 项目地址: https://ai.gitcode.com/hf_mirrors/dx8152/Fusion_lora 在电商设计和产品展示领域&#xff0c;传统图像融合技术面临着透视匹配不精准、光影效果不自然、操作流程…

作者头像 李华
网站建设 2026/3/3 17:17:03

跨平台剪贴板操作终极指南:快速上手Pyperclip

跨平台剪贴板操作终极指南&#xff1a;快速上手Pyperclip 【免费下载链接】pyperclip Python module for cross-platform clipboard functions. 项目地址: https://gitcode.com/gh_mirrors/py/pyperclip Pyperclip是一个专门为Python开发者设计的跨平台剪贴板操作库&…

作者头像 李华
网站建设 2026/3/3 14:01:45

MlFinLab实战指南:打造专业级量化投资策略的完整工具箱

MlFinLab实战指南&#xff1a;打造专业级量化投资策略的完整工具箱 【免费下载链接】mlfinlab MlFinLab helps portfolio managers and traders who want to leverage the power of machine learning by providing reproducible, interpretable, and easy to use tools. 项目…

作者头像 李华
网站建设 2026/3/3 14:01:50

联想LJ2605D LJ2655DN激光打印机维修与故障排除完全指南

联想LJ2605D LJ2655DN激光打印机维修与故障排除完全指南 【免费下载链接】联想LJ2605DLJ2655DN中文维修手册分享 联想LJ2605D LJ2655DN中文维修手册欢迎来到联想LJ2605D与LJ2655DN激光打印机的中文维修手册下载页面 项目地址: https://gitcode.com/Open-source-documentation…

作者头像 李华