当前位置: 首页 > 科技观察

Go函数的Map参数扩容后会指向不同的底层内存吗?

时间:2023-03-20 14:24:14 科技观察

本文转载自微信公众号《网管唠叨bi唠叨》,作者KevinYan11。转载本文请联系网管谢bi公众号。最近,我和同事一起做一个项目。由于函数中需要将大量数据写入一个Map,所以将这个Map作为参数传递给函数。他问了我一个问题:“如果把Map作为函数参数传递,会不会像用Slice作为参数一样奇怪?是不是需要把Map作为返回值返回,让函数外的Map变量可以看到值这里添加了“数据”?这是什么意思?会不会像使用Slice作为参数一样奇怪?同事没说清楚,但我已经猜到他的意思了。应该是,如果扩展了Slice的底层数组,函数会将原本指向同一个底层数组的两个Slice变量,里外,分别指向两个不同的底层数组。最后导致在函数内部增加了数据,但是函数外部原来的Slice变量并没有什么奇怪的变化效果。光看文字有点难以解释。例如,有一个像下面这样的程序。funcmain(){s:=[]int{1,2,3}reverse(s)fmt.Println(s)}funcreverse(s[]int){s=append(s,999,1000,1001)对于i,j:=0,len(s)-1;我maxAlloc{hint=0}......returnh}通过上面的解释和代码,我们了解到Map的数据类型在运行时其实是一个hmap类型的指针,只是在我们写代码的时候隐藏起来了.由于Map类型的变量实际上是一个指针变量,这与Slice是完全不同的。虽然在Go中指针作为函数参数按值传递,但内部和外部指针指向同一个hamp结构。hmap结构中有很多字段。要回答这里的问题,我们只需要知道buckets和oldbuckets这两个指针类型的字段即可。typehmapstruct{countintflagsuint8Buint8noverflowuint16hash0uint32bucketsunsafe.Pointeroldbucketsunsafe.Pointernevacuateuintptrextra*mapextra}Go的Map中用来存储键值对数据的结构——bucket(bmap),对于bmap我们不再深入挖掘。buckets是一个桶数组。当哈希表增长到需要扩容的时候,Go语言会将桶数组的数量翻倍,生成一个新的桶数组。旧数据存储在oldbuckets指向的bucket中,访问时迁移到新bucket中去。这里虽然扩容导致Map有了新的bucket数组的地址,但是这个地址保存在hmap的字段buckets中,改变字段的值不会影响hmap本身的内存地址。因此,当Map因函数中的操作而展开时,不会出现上例中的Slice指向不同底层数组的奇怪现象。不知道大家有没有看懂我这里的分析。这篇文章其实是自己对问题思考的记录,以防时间长了忘记了。按值传递和按引用传递在不同的语言中是不同的。对于像我们这样至少掌握了三种编程语言的人:)只能靠写笔记来防止混淆了。(我相信绝大部分人的职业生涯不可能只靠一种编程语言来吃天。)还有一点我觉得Go的Slice使用起来确实很耗费脑力,而且很容易踩坑如果你不注意。时间长了,大家在用Map和指针做参数的时候都会怀疑自己。希望本文能帮助您解决使用它们的疑惑。参考地址Go语言设计与实现——哈希表https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-hashmap