news 2026/5/1 5:46:10

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/4/23 19:13:40

3分钟掌握Chosen.js:打造现代化选择框的完整指南

3分钟掌握Chosen.js&#xff1a;打造现代化选择框的完整指南 【免费下载链接】chosen Deprecated - Chosen is a library for making long, unwieldy select boxes more friendly. 项目地址: https://gitcode.com/gh_mirrors/ch/chosen 在当今Web开发中&#xff0c;表单…

作者头像 李华
网站建设 2026/4/28 22:33:06

终极交互式图表设计:Charticulator完全实战指南

终极交互式图表设计&#xff1a;Charticulator完全实战指南 【免费下载链接】charticulator Interactive Layout-Aware Construction of Bespoke Charts 项目地址: https://gitcode.com/gh_mirrors/ch/charticulator 在数据可视化领域&#xff0c;Charticulator作为一款…

作者头像 李华
网站建设 2026/4/29 14:13:18

DockPanel Suite 终极使用指南:从入门到精通

DockPanel Suite 终极使用指南&#xff1a;从入门到精通 【免费下载链接】dockpanelsuite DockPanelSuite: DockPanelSuite 是一个受 Visual Studio 启发的用于 .NET WinForms 的停靠库&#xff0c;允许开发者在他们的应用程序中实现复杂的用户界面布局。 项目地址: https://…

作者头像 李华
网站建设 2026/4/13 14:02:28

Ant Design Landing终极指南:快速构建企业级网页组件库

Ant Design Landing终极指南&#xff1a;快速构建企业级网页组件库 【免费下载链接】ant-design-landing :mountain_bicyclist: Landing Pages of Ant Design System 项目地址: https://gitcode.com/gh_mirrors/an/ant-design-landing 在当今快节奏的数字化时代&#xf…

作者头像 李华
网站建设 2026/5/1 5:43:05

Boulder完整测试策略指南:构建企业级证书颁发系统的质量保障体系

Boulder作为基于ACME协议的证书颁发机构&#xff0c;采用精心设计的测试策略确保系统在复杂环境中的稳定运行。这套多层次质量保障体系覆盖了从代码单元到完整业务流程的各个环节&#xff0c;为证书颁发服务提供了坚实的技术支撑。 【免费下载链接】boulder An ACME-based cert…

作者头像 李华
网站建设 2026/4/26 23:51:31

高效FourierKAN实战:构建智能神经网络层的实用方案

高效FourierKAN实战&#xff1a;构建智能神经网络层的实用方案 【免费下载链接】FourierKAN 项目地址: https://gitcode.com/GitHub_Trending/fo/FourierKAN FourierKAN是一个基于PyTorch的高效神经网络层实现&#xff0c;它通过傅里叶变换技术为传统线性层激活函数组合…

作者头像 李华