当前位置: 首页 > news >正文

Golang Map 深度剖析:原理、实践与面试要点

嘿,小伙伴们!我是 k 哥。今天,咱们来聊聊 Map 。

在 Go 语言这个神奇的世界里,Map 这个有点神秘的数据结构一直都是开发者们特别关注的。

你是不是在用 Map 的时候,对它里面咋工作的感到好奇?是不是碰到复杂操作的时候,特别想弄明白它背后的原理?别着急,今天这篇文章就带你走进 Go 语言 Map 那个神秘的世界,帮你一层一层揭开它的面纱!

从底层的原理,到最佳实践,再到高频面试题的分析,这篇文章会从各个方面满足你的求知心。不管你是刚学的新手,还是经验丰富的老手,相信都能从这里得到宝贵的知识,受到启发。准备好跟我一起开始这场精彩的探索旅程了不?

1 原理

1.1 数据结构
在这里插入图片描述

Map 所涉及的核心数据结构包括两个,即 hmap 和 bmap :

  1. 每当创建一个 map 对象时,在底层都会创建一个 hmap 结构,以用于存储 map 的长度、hash 种子、状态等基础信息。

  2. hmap 指针类型的成员变量 buckets ,指向 bmap 数组。bmap 用于存储键值对。对于相同的键,每次进行 hash 操作后都会定位到 buckets 相同的索引位置进行访问。

  3. 每个 bmap 能够存储 8 个键值对,并且,每个 bmap 设有一个指针,当某个 bmap 存满时,就会申请新的 bmap 进行存储,并与前一个 bmap 构成链表。(通过链地址法解决冲突)

1.1.1 hmap

const (// Maximum average load of a bucket that triggers growth is 6.5.// Represent as loadFactorNum/loadFactorDen, to allow integer math.loadFactorNum = 13loadFactorDen = 2)// A header for a Go map.
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     uint8B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for detailshash0     uint32 // hash seedbuckets    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 growingnevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)extra *mapextra // optional fields
}// mapextra holds fields that are not present on all maps.
type mapextra struct {// If both key and elem do not contain pointers and are inline, then we mark bucket// type as containing no pointers. This avoids scanning such maps.// However, bmap.overflow is a pointer. In order to keep overflow buckets// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.// overflow and oldoverflow are only used if key and elem do not contain pointers.// overflow contains overflow buckets for hmap.buckets.// oldoverflow contains overflow buckets for hmap.oldbuckets.// The indirection allows to store a pointer to the slice in hiter.overflow    *[]*bmap // 溢出桶数组指针oldoverflow *[]*bmap // 迁移过程中,旧溢出桶数组指针// nextOverflow holds a pointer to a free overflow bucket.nextOverflow *bmap // 还未使用的,提前分配的溢出桶链表
}

每创建一个 map 对象,在 Go 语言底层都会创建 hmap 结构,其成员的含义如下:

  1. count :表示 map 中的数据个数,与 len() 函数相对应。

  2. flags :属于标记字段,用于标记是否正在进行读写操作,以便实现并发读写的检测。

  3. B :代表桶的数量,hash 桶 buckets 的数量为 2^B 个。

  4. noverflow :是溢出桶数量的近似值。

  5. hash0 :为 hash 种子,用于计算 key 的 hash 值。

  6. buckets :是指向由 2^B 个桶所组成的数组的指针。

  7. oldbuckets :指向扩容前的旧 buckets 数组,仅在 map 扩容时发挥作用。

  8. nevacuate :作为计数器,用于标示扩容后搬迁的进度,服务于渐进式迁移。

  9. extra :用于保存溢出桶和未使用溢出桶切片的首地址。

1.1.2 bmap

在这里插入图片描述


const (// Maximum number of key/elem pairs a bucket can hold.bucketCntBits = 3bucketCnt     = 1 << bucketCntBits
)// A bucket for a Go map.
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 [bucketCnt]uint8// Followed by bucketCnt keys and then bucketCnt elems.// Followed by an overflow pointer.
}

bmap 主要用于存储 key-value 对,每个 bmap 能够存储 8 个 key-value 对。bmap 包含 4 个成员变量(尽管在源码中只有一个成员变量 tophash,但实际上在申请内存时,Go 语言会依据 key、value 的具体类型,额外为另外 3 个成员变量分配内存):

  1. tophash 数组,用于存储每个 key hash 之后的高位 hash 值。

  2. key 数组,用于存储 key。

  3. value 数组,用于存储 value。

  4. overflow 溢出指针,指向了下一个 bmap 的地址。

bmap 有个溢出桶指针能指向溢出桶,那 extra 里为啥还得用 *[]*bmap 结构来存所有的溢出桶呢?

这主要是因为 gc 的原因。在早前的 Go 语言版本里,gc 会把 buckets 里的所有对象都扫一遍。要是 map 存的 key-value 对特别多,gc 能花上几百毫秒到好几秒,这就会让一些用 Go 语言开发的服务,接口超时超得厉害。

为了处理这个情况,Go 语言官方改了 map 的实现。要是 map 满足下面这两个条件,那 bmap 里除了 overflow ,就没别的指针了,而且会被 gc 标记成不用扫描:

  • key 和 value 不是指针类型,里面也不含指针(int 类型行,string 类型不行,因为 string 底层的数据结构里有指针)。

  • key 和 value 的大小得小于 128 字节。

但是 bmap.overflow 是指针类型,如果 gc 不扫 buckets ,溢出桶就可能被 gc 错误地回收掉。为了不让这种情况发生,就在 extra 里用 *[]*bmap 来存溢出桶,这样 gc 就能通过 []*bmap 扫到溢出桶(不用扫桶里面的东西),也就不会把溢出桶错误回收了。

1.2 插入或更新

1.2.1 2种异常情况处理

// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {// 为nil则panicif h == nil {panic(plainError("assignment to entry in nil map"))}// 并发读写会抛异常,且不能被defer捕获if h.flags&hashWriting != 0 {throw("concurrent map writes")}// 计算key对应的hash值hash := t.hasher(key, uintptr(h.hash0))// 设置正在写标记h.flags ^= hashWriting// 初始化,但是桶为空的,会自动创建桶数组,读写不会panicif h.buckets == nil {

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 计算机毕业设计选题推荐-springboot 基于springboot的宠物健康顾问系统
  • Docker的Fig
  • 详解golang内存管理
  • AttributeError: module numpy has no attribute int报错
  • python 获取pdf文件中的超链接
  • 14、springboot3 vue3开发平台-前端-自定义菜单组件,根据路由动态渲染
  • 如何保证Redis缓存和数据库的数据一致性
  • Redmi 13C 5G 红米13R 5G 解锁BL 降级 MIUI 秒解锁BL 澎湃OS 降级
  • 8-5 循环神经网络 RNN 的实现
  • 利用java结合python实现gis在线绘图,主要技术java+python+matlab+idw+Kriging
  • 【JAVA基础】从内部类引用的局部变量必须是final或有效的final
  • 86.小米相机修改拍照(尺寸,画幅,比例)的方法
  • SAP B1系统设置和管理——数据所有权权限
  • 技术革新!MultiDesk:高效远程桌面管理工具,TAB切换引领新潮流!
  • 24/8/18算法笔记 MARL多智能体算法
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • js算法-归并排序(merge_sort)
  • mongo索引构建
  • Nacos系列:Nacos的Java SDK使用
  • python_bomb----数据类型总结
  • Python爬虫--- 1.3 BS4库的解析器
  • React-Native - 收藏集 - 掘金
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • swift基础之_对象 实例方法 对象方法。
  • 爱情 北京女病人
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 关键词挖掘技术哪家强(一)基于node.js技术开发一个关键字查询工具
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 简单数学运算程序(不定期更新)
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • #1015 : KMP算法
  • #在 README.md 中生成项目目录结构
  • ${factoryList }后面有空格不影响
  • $nextTick的使用场景介绍
  • (Java)【深基9.例1】选举学生会
  • (M)unity2D敌人的创建、人物属性设置,遇敌掉血
  • (Oracle)SQL优化技巧(一):分页查询
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (八)Flink Join 连接
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (四)opengl函数加载和错误处理
  • (五)activiti-modeler 编辑器初步优化
  • (转)Oracle存储过程编写经验和优化措施
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET 跨平台图形库 SkiaSharp 基础应用
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • @property @synthesize @dynamic 及相关属性作用探究