千家信息网

如何正确使用Go Map

发表于:2025-01-17 作者:千家信息网编辑
千家信息网最后更新 2025年01月17日,本篇内容主要讲解"如何正确使用Go Map",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"如何正确使用Go Map"吧!前言例子如下:func main()
千家信息网最后更新 2025年01月17日如何正确使用Go Map

本篇内容主要讲解"如何正确使用Go Map",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"如何正确使用Go Map"吧!

前言

例子如下:

func main() {  m := make(map[int32]string)  m[0] = "EDDYCJY1"  m[1] = "EDDYCJY2"  m[2] = "EDDYCJY3"  m[3] = "EDDYCJY4"  m[4] = "EDDYCJY5"   for k, v := range m {   log.Printf("k: %v, v: %v", k, v)  } }

假设运行这段代码,输出的结果是怎么样?是有序,还是无序输出呢?

k: 3, v: EDDYCJY4 k: 4, v: EDDYCJY5 k: 0, v: EDDYCJY1 k: 1, v: EDDYCJY2 k: 2, v: EDDYCJY3

从输出结果上来讲,是非固定顺序输出的,也就是每次都不一样。但这是为什么呢?

首先建议你先自己想想原因。其次我在面试时听过一些说法。有人说因为是哈希的所以就是无(乱)序等等说法。当时我是有点 ???

这也是这篇文章出现的原因,希望大家可以一起研讨一下,理清这个问题 :)

看一下汇编

   ... 0x009b 00155 (main.go:11) LEAQ type.map[int32]string(SB), AX 0x00a2 00162 (main.go:11) PCDATA $2, $0 0x00a2 00162 (main.go:11) MOVQ AX, (SP) 0x00a6 00166 (main.go:11) PCDATA $2, $2 0x00a6 00166 (main.go:11) LEAQ ""..autotmp_3+24(SP), AX 0x00ab 00171 (main.go:11) PCDATA $2, $0 0x00ab 00171 (main.go:11) MOVQ AX, 8(SP) 0x00b0 00176 (main.go:11) PCDATA $2, $2 0x00b0 00176 (main.go:11) LEAQ ""..autotmp_2+72(SP), AX 0x00b5 00181 (main.go:11) PCDATA $2, $0 0x00b5 00181 (main.go:11) MOVQ AX, 16(SP) 0x00ba 00186 (main.go:11) CALL runtime.mapiterinit(SB) 0x00bf 00191 (main.go:11) JMP 207 0x00c1 00193 (main.go:11) PCDATA $2, $2 0x00c1 00193 (main.go:11) LEAQ ""..autotmp_2+72(SP), AX 0x00c6 00198 (main.go:11) PCDATA $2, $0 0x00c6 00198 (main.go:11) MOVQ AX, (SP) 0x00ca 00202 (main.go:11) CALL runtime.mapiternext(SB) 0x00cf 00207 (main.go:11) CMPQ ""..autotmp_2+72(SP), $0 0x00d5 00213 (main.go:11) JNE 193 ...

我们大致看一下整体过程,重点处理 Go map 循环迭代的是两个 runtime 方法,如下:

  • runtime.mapiterinit

  • runtime.mapiternext

但你可能会想,明明用的是 for range 进行循环迭代,怎么出现了这两个函数,怎么回事?

看一下转换后

var hiter map_iteration_struct for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {     index_temp = *hiter.key     value_temp = *hiter.val     index = index_temp     value = value_temp     original body }

实际上编译器对于 slice 和 map 的循环迭代有不同的实现方式,并不是 for 一扔就完事了,还做了一些附加动作进行处理。而上述代码就是 for range map 在编译器展开后的伪实现

看一下源码

runtime.mapiterinit

func mapiterinit(t *maptype, h *hmap, it *hiter) {  ...  it.t = t  it.h = h  it.B = h.B  it.buckets = h.buckets  if t.bucket.kind&kindNoPointers != 0 {   h.createOverflow()   it.overflow = h.extra.overflow   it.oldoverflow = h.extra.oldoverflow  }   r := uintptr(fastrand())  if h.B > 31-bucketCntBits {   r += uintptr(fastrand()) << 31  }  it.startBucket = r & bucketMask(h.B)  it.offset = uint8(r >> h.B & (bucketCnt - 1))  it.bucket = it.startBucket     ...   mapiternext(it) }

通过对 mapiterinit 方法阅读,可得知其主要用途是在 map 进行遍历迭代时进行初始化动作。共有三个形参,用于读取当前哈希表的类型信息、当前哈希表的存储信息和当前遍历迭代的数据

为什么

咱们关注到源码中 fastrand 的部分,这个方法名,是不是迷之眼熟。没错,它是一个生成随机数的方法。再看看上下文:

... // decide where to start r := uintptr(fastrand()) if h.B > 31-bucketCntBits {  r += uintptr(fastrand()) << 31 } it.startBucket = r & bucketMask(h.B) it.offset = uint8(r >> h.B & (bucketCnt - 1))  // iterator state it.bucket = it.startBucket

在这段代码中,它生成了随机数。用于决定从哪里开始循环迭代。更具体的话就是根据随机数,选择一个桶位置作为起始点进行遍历迭代

因此每次重新 for range map,你见到的结果都是不一样的。那是因为它的起始位置根本就不固定!

runtime.mapiternext

func mapiternext(it *hiter) {     ...     for ; i < bucketCnt; i++ {   ...   k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize))   v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(offi)*uintptr(t.valuesize))   ...   if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) ||    !(t.reflexivekey || alg.equal(k, k)) {    ...    it.key = k    it.value = v   } else {    rk, rv := mapaccessK(t, h, k)    if rk == nil {     continue // key has been deleted    }    it.key = rk    it.value = rv   }   it.bucket = bucket   if it.bptr != b {    it.bptr = b   }   it.i = i + 1   it.checkBucket = checkBucket   return  }  b = b.overflow(t)  i = 0  goto next }

到此,相信大家对"如何正确使用Go Map"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

迭代 方法 h.B 循环 输出 代码 就是 结果 随机数 哈希 两个 位置 信息 内容 动作 原因 实际 源码 编译器 说法 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 企业网络安全分析解决方案 sql服务器事务问题 直销软件开发收费多少 带括号的数字怎么存进数据库 游戏时服务器连接方法 学软件开发培训班有哪些 衡水做app的软件开发费用 智慧景区软件开发知名品牌 河南通信软件开发过程参考价格 合肥齐美软件开发有限公司 曲靖市网络安全应急指挥中心 数据库建表中的说明怎么写 网络安全员工找工作 北京数据库防护箱价目表 数据库窗体设计视图添加命令 v2下载是服务器的下行吗 服务器搭建工具 学校网络安全领导组成立情况 机房服务器方案 数据库一对多关系转换 日产逍客音乐找不到服务器 普法课堂网络安全教程 计算机网络技术属于数学类吗 点开数据网络安全吗 云服务器内存怎么看 专技天下网考试网络安全答案 浏览器未连接上服务器是什么问题 三级网络技术应用题第三套 网上售票票数据库设计 幻塔如何查看之前的服务器
0