news 2026/2/5 19:18:03

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/2/2 4:42:47

LoopScrollRect循环滚动优化5大技巧:Unity性能优化终极指南

还在为Unity中大量UI元素的滚动卡顿而烦恼吗&#xff1f;LoopScrollRect作为UGUI系统的强力扩展&#xff0c;通过智能单元格复用机制彻底解决了传统ScrollRect在大数据量场景下的性能瓶颈。无论您是游戏开发者还是应用设计师&#xff0c;这款插件都能让您的UI滚动体验实现质的飞…

作者头像 李华
网站建设 2026/2/5 16:53:04

VCR开源贡献之旅:从代码新手到社区核心成员

在数字世界的浩瀚星空中&#xff0c;开源项目如同璀璨的星辰&#xff0c;而VCR正是其中一颗闪耀的明星。这个强大的HTTP测试录制工具不仅改变了测试方式&#xff0c;更凝聚了全球开发者的智慧与热情。今天&#xff0c;让我们一起踏上这段充满挑战与成就的开源贡献之旅。 【免费…

作者头像 李华
网站建设 2026/2/2 23:30:32

ISO/IEC 27005:2022终极指南 - 信息安全风险管理的权威实践手册

ISO/IEC 27005:2022终极指南 - 信息安全风险管理的权威实践手册 【免费下载链接】ISOIEC270052022英文PDF原版下载仓库 探索信息安全风险管理的核心指南&#xff01;ISO/IEC 27005:2022是信息安全、网络空间安全及隐私保护领域的权威文件&#xff0c;提供全面的风险管理框架和方…

作者头像 李华
网站建设 2026/2/5 6:14:59

暗黑破坏神1移植指南:在Switch上重温经典ARPG

暗黑破坏神1移植指南&#xff1a;在Switch上重温经典ARPG 【免费下载链接】devilutionX Diablo build for modern operating systems 项目地址: https://gitcode.com/gh_mirrors/de/devilutionX 想在任天堂Switch上体验原汁原味的暗黑破坏神1吗&#xff1f;DevilutionX项…

作者头像 李华
网站建设 2026/2/2 23:30:29

5分钟掌握jQuery人脸检测:从零构建智能图像处理应用

5分钟掌握jQuery人脸检测&#xff1a;从零构建智能图像处理应用 【免费下载链接】jquery.facedetection 项目地址: https://gitcode.com/gh_mirrors/jq/jquery.facedetection 在当今的Web开发中&#xff0c;人脸检测技术正迅速成为图像处理应用的核心功能。jQuery Face…

作者头像 李华
网站建设 2026/2/5 0:54:34

将照片转化为线条艺术的终极指南:Pintr项目深度解析

将照片转化为线条艺术的终极指南&#xff1a;Pintr项目深度解析 【免费下载链接】pintr Create single line illustrations from your pictures. Get a drawing, SVG or coordinates for a CNC. 项目地址: https://gitcode.com/gh_mirrors/pi/pintr 在数字艺术创作领域&…

作者头像 李华