go 切片取最后一个元素_深挖 Go 之 forrange 排坑指南

今年做个 Dig101 系列,挖一挖技术背后的故事。

Dig101: dig more, simplified more and know more

golang 常用的遍历方式,有两种:for 和 for-range。而 for-range 使用中有些坑常会遇到,今天我们一起来捋一捋。

文章目录

  • 0x01 遍历取不到所有元素指针?

  • 0x02 遍历会停止么?

  • 0x03 对大数组这样遍历有啥问题?

  • 0x04 对大数组这样重置效率高么?

  • 0x05 对 map 遍历时删除元素能遍历到么?

  • 0x06 对 map 遍历时新增元素能遍历到么?

  • 0x07 这样遍历中起 goroutine 可以么?

0x01 遍历取不到所有元素指针?

如下代码想从数组遍历获取一个指针元素切片集合
arr := [2]int{1, 2}
res := []*int{}
for _, v := range arr {
res = append(res, &v)
}
//expect: 1 2
fmt.Println(*res[0],*res[1])
//but output: 2 2

答案是【取不到】 同样代码对切片[]int{1, 2}map[int]int{1:1, 2:2}遍历也不符合预期。问题出在哪里?

通过查看go 编译源码[1]可以了解到, for-range 其实是语法糖,内部调用还是 for 循环,初始化会拷贝带遍历的列表(如 array,slice,map),然后每次遍历的v都是对同一个元素的遍历赋值。也就是说如果直接对v取地址,最终只会拿到一个地址,而对应的值就是最后遍历的那个元素所附给v的值。对应伪代码如下:

// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }

那么怎么改?有两种

  • 使用局部变量拷贝v
for _, v := range arr {
//局部变量v替换了v,也可用别的局部变量名
v := v
res = append(res, &v)
}
  • 直接索引获取原来的元素
//这种其实退化为for循环的简写
for k := range arr {
res = append(res, &arr[k])
}

理顺了这个问题后边的坑基本都好发现了,来迅速过一遍

0x02 遍历会停止么?

v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}

答案是【会】,因为遍历前对v做了拷贝,所以期间对原来v的修改不会反映到遍历中

0x03 对大数组这样遍历有啥问题?

//假设值都为1,这里只赋值3个
var arr = [102400]int{1, 1, 1}
for i, n := range arr {
//just ignore i and n for simplify the example
_ = i
_ = n
}

答案是【有问题】!遍历前的拷贝对内存是极大浪费啊 怎么优化?有两种

  • 对数组取地址遍历for i, n := range &arr
  • 对数组做切片引用for i, n := range arr[:]

反思题:对大量元素的 slice 和 map 遍历为啥不会有内存浪费问题?(提示,底层数据结构是否被拷贝)

0x04 对大数组这样重置效率高么?

//假设值都为1,这里只赋值3个
var arr = [102400]int{1, 1, 1}
for i, _ := range &arr {
arr[i] = 0
}

答案是【高】,这个要理解得知道 go 对这种重置元素值为默认值的遍历是有优化的, 详见go 源码:memclrrange[2]

// Lower n into runtime·memclr if possible, for
// fast zeroing of slices and arrays (issue 5373).
// Look for instances of
//
// for i := range a {
// a[i] = zero
// }
//
// in which the evaluation of a is side-effect-free.

0x05 对 map 遍历时删除元素能遍历到么?

var m = map[int]int{1: 1, 2: 2, 3: 3}
//only del key once, and not del the current iteration key
var o sync.Once
for i := range m {
o.Do(func() {
for _, key := range []int{1, 2, 3} {
if key != i {
fmt.Printf("when iteration key %d, del key %d\n", i, key)
delete(m, key)
break
}
}
})
fmt.Printf("%d%d ", i, m[i])
}

答案是【不会】 map 内部实现是一个链式 hash 表,为保证每次无序,初始化时会随机一个遍历开始的位置[3], 这样,如果删除的元素开始没被遍历到(上边once.Do函数内保证第一次执行时删除未遍历的一个元素),那就后边就不会出现。

0x06 对 map 遍历时新增元素能遍历到么?

var m = map[int]int{1:1, 2:2, 3:3}
for i, _ := range m {
m[4] = 4
fmt.Printf("%d%d ", i, m[i])
}

答案是【可能会】,输出中可能会有44。原因同上一个, 可以用以下代码验证

var createElemDuringIterMap = func() {
var m = map[int]int{1: 1, 2: 2, 3: 3}
for i := range m {
m[4] = 4
fmt.Printf("%d%d ", i, m[i])
}
}
for i := 0; i < 50; i++ {
//some line will not show 44, some line will
createElemDuringIterMap()
fmt.Println()
}

0x07 这样遍历中起 goroutine 可以么?

var m = []int{1, 2, 3}
for i := range m {
go func() {
fmt.Print(i)
}()
}
//block main 1ms to wait goroutine finished
time.Sleep(time.Millisecond)

答案是【不可以】。预期输出 0,1,2 的某个组合,如 012,210.. 结果是 222. 同样是拷贝的问题 怎么解决

  • 以参数方式传入
for i := range m {
go func(i int) {
fmt.Print(i)
}(i)
}
  • 使用局部变量拷贝
for i := range m {
i := i
go func() {
fmt.Print(i)
}()
}
发现没,一个简单的 for-range,仔细剖析下来也是有不少有趣的地方。希望剖析后能让你更进一步的了解。如有问题欢迎留言交流。

See more:Go Range Loop Internals[4],Common Mistakes[5],go101: Arrays, Slices and Maps in Go[6]

推荐阅读

  • 深入 Go 内存分配超级棒的文章:Go 内存分配器可视化指南


喜欢本文的朋友,欢迎关注“Go语言中文网”:

78a0a99ff18453802d4db462c9827fcd.png

Go语言中文网启用微信学习交流群,欢迎加微信:274768166

参考资料

[1]

go编译源码: https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5501

[2]

go源码:memclrrange: https://github.com/golang/go/blob/ea020ff3de9482726ce7019ac43c1d301ce5e3de/src/cmd/compile/internal/gc/range.go#L363

[3]

随机一个遍历开始的位置: https://github.com/golang/go/blob/0bd3853512ea0dcb252ce02113d3929db03d6aa6/src/runtime/map.go#L826

[4]

Go Range Loop Internals: https://garbagecollected.org/2017/02/22/go-range-loop-internals/

[5]

Common Mistakes: https://github.com/golang/go/wiki/CommonMistakes

[6]

go101: Arrays, Slices and Maps in Go: https://go101.org/article/container.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302412.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SqlServer SqlParser 介绍及基本使用

SqlServer SqlParser 介绍及使用示例Intro最近发现在 Nuget 上有一个 SqlServer 的 SqlParser&#xff0c;利用 SqlParser 我们做到可以解析 SQL 的每一部分 &#xff0c;nuget 包是公开的&#xff0c;可以拿来即用&#xff0c;只是缺少使用示例&#xff0c;很多功能需要自己去…

如何使用TensorFlow玩转深度学习?

自 2015 年 11 月 9 号发布之后&#xff0c;TensorFlow 逐渐成为人工智能领域最广泛运用的深度学习框架。那么TensorFlow框架到底是什么&#xff1f;TensorFlow 是一个大规模机器学习的开源框架&#xff0c;提供了多种深度神经网络的支持。不仅 Google 在自己的产品线使用 Tens…

大学,我是怎么边学编程边赚钱的?

我是如何在大学时就靠编程赚钱的&#xff1f;大家好&#xff0c;我是鱼皮&#xff0c;前段时间看到一位朋友的问题&#xff1a;穷极客一枚&#xff0c;正值大学&#xff0c;很想自己解决生活问题&#xff0c;不再向父母要钱。计算机相关专业&#xff0c;喜欢编程&#xff0c;觉…

独占设备的分配与回收_灵魂拷问:Java对象的内存分配过程是如何保证线程安全的?...

点击上方“linkoffer”&#xff0c;选择关注公众号高薪职位第一时间送达作者 l HollisJVM内存结构&#xff0c;是很重要的知识&#xff0c;相信每一个静心准备过面试的程序员都可以清楚的把堆、栈、方法区等介绍的比较清楚。上图&#xff0c;是一张在作者根据《Java虚拟机规范(…

Wtm Blazor来了!

快点关注我们吧BlazorBlazor从诞生到现在也有一段时间了&#xff0c;之前一直在观望&#xff0c;从dotnet5中Blazor的进步以及即将到来的dotnet6中的规划来看&#xff0c;Blazor的前途还是光明的&#xff0c;所以WtmBlazor来了&#xff01;Blazor的优势后台代码的高复用率。不论…

[Netty实践] 简单WebSocket服务实现

目录 一、介绍 二、依赖导入 三、基础类准备 四、Handler实现 五、WebSocketChannelInitializer实现 六、WebSocketServer实现 七、前端实现 八、测试 九、参考链接 一、介绍 关于WebSocket此处不进行过多介绍&#xff0c;本章主要着重通过Netty实现WebSocket通信服务…

这个爱喝酒的酒鬼可真是让人操碎了心

全世界只有3.14 % 的人关注了数据与算法之美最近又有一道数学难题重现江湖&#xff0c;在数学的江湖上掀起了腥风血雨。为了这道题&#xff0c;武林中也衍生出了三个门派&#xff01;分别有75%派&#xff0c;90%派&#xff0c;50%派。打完这么多派字&#xff0c;怎么莫名有点饿…

这几家5月还在急招.NET,都是30k以上!

最近常看到鼓吹财务自由的文章&#xff0c;甚至将5月18号(谐音&#xff1a;我要发)都演变成了财务自由日&#xff0c;号称通过理财快速达到财务自由... 荒谬&#xff01;财务自由本身就是伪命题&#xff0c;更不提啥小白理财就变身财务自由了&#xff0c;完全收智商税&#xff…

mysql binlog oplog_mongodb 学习之oplog

背景&#xff1a;原来一个同事问我主从mongodb数据库为什么数据差距很大,我让他察看一下两边有啥不一样&#xff0c;发现主的local库有13G从却很小&#xff0c;进入local之后du发现有一个collection前缀的文件有13g&#xff0c;说明是local数据库中一个集合太大了&#xff0c;推…

WPF实现Map加载

WPF开发者QQ群&#xff1a; 340500857 欢迎转发、分享、点赞&#xff0c;谢谢大家~。 接着上一篇效果预览&#xff1a;一、MainWindow.xaml代码如下&#xff1a;<Window x:Class"WpfBingMap.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml…

和哪个专业的男生谈恋爱最惨?

全世界只有3.14 % 的人关注了数据与算法之美艺术类专业艺术类的男生是最懂女孩们的心思&#xff0c;也是最浪漫的一类人群&#xff0c;弹琴唱歌跳舞画画样样擅长。这类男生所做的一切&#xff0c;皆可以把女孩们的心俘获到。但是呢&#xff0c;这类男孩的身边总是会有很多玩的很…

只能选择分卷文件的第一部分。_为机器学习模型选择正确的度量评估(第一部分)...

作者&#xff1a;Alvira Swalin编译&#xff1a;ronghuaiyang导读对不同的应用场景&#xff0c;需要不同的模型&#xff0c;对于不同的模型&#xff0c;需要不同的度量评估方式。本系列的第一部分主要关注回归的度量在后现代主义的世界里&#xff0c;相对主义的各种形式一直是最…

多项式乘法与快速傅里叶变换

全世界只有3.14 % 的人关注了数据与算法之美第一节、多项式乘法我们知道&#xff0c;有两种表示多项式的方法&#xff0c;即系数表示法和点值表示法。什么是系数表示法?所谓的系数表示法&#xff0c;举个例子如下图所示&#xff0c;A&#xff08;x&#xff09;6x^3 7x^2 - 10…

WPF 模仿QQ音乐首页歌单效果

qq音乐桌面版做的效果感觉很不错&#xff0c;今天就模仿一下它首页歌单的效果&#xff0c;从简单做起。。。看一下效果&#xff1a;&#xff0c;其实也很简单&#xff0c;就是布局和动画&#xff0c;触发器。。。还用到了ItemsControl下面就看看代码&#xff1a;MainWindow的xa…

收藏 : 50个Excel逆天功能,一秒变“表哥”

全世界只有3.14 % 的人关注了数据与算法之美Excel的50个逆天功能&#xff0c;动画教程珍藏版&#xff01;先看几个简单的&#xff1a;1、自动筛选2、在Excel中字符替换3、在Excel中冻结行列标题4、在Excel中为导入外部数据5、在Excel中行列快速转换6、共享Excel工作簿7、在Exce…

实战~~整个网络无法浏览,提示网络不存在或者尚未启动

今天早上接到同事的电脑&#xff0c;说其他人访问不到他的电脑&#xff0c;他电脑上有文件要共享才能进行工作~~故障现象&#xff1a;能上网&#xff0c;能PING通其他电脑&#xff0c;但是通过网上邻居和IP不能访问其他电脑上的资源。 这是在故障本机上的提示~~ 这是其他工作站…

python ctp接口_使用ctp的python接口

在github上查到一个项目ctpwrapper在按照文档按照的时候报错>>>pip install cython --upgrade>>>pip install ctpwrapper --upgrade在安装第二个命令的时候第一个问题安装yum install -y gcc-c 解决第二个问题ctpwrapper/MdApi.cpp:39:20: 致命错误:Python.h…

C# 并行和多线程编程——认识和使用Task

对于多线程&#xff0c;我们经常使用的是Thread。在我们了解Task之前&#xff0c;如果我们要使用多核的功能可能就会自己来开线程&#xff0c;然而这种线程模型在.net 4.0之后被一种称为基于“任务的编程模型”所冲击&#xff0c;因为task会比thread具有更小的性能开销&#xf…

Facebook上的一道题,超过50万的评论和1万3500次分享

全世界只有3.14 % 的人关注了数据与算法之美近日&#xff0c;有网友在Facebook发了一道数学题&#xff1a;发布以后&#xff0c;目前已经收到超过50万的评论和1万3500次分享&#xff0c;图中包含四个等式&#xff0c;前面三个已经有答案了&#xff0c;最后一个问题要求你得出相…

从数学入手,3招打破机器学习的边界

全世界只有3.14 % 的人关注了数据与算法之美本文约2007余字&#xff0c;阅读需要约6分钟&#xff1b;系统资料领取见文末&#xff1b;关键词&#xff1a;人工智能&#xff0c;机器学习&#xff0c;深度学习&#xff0c;数学&#xff0c;学习建议01.机器学习工程师的边界是什么&…