今年做个 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 2fmt.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 keyvar o sync.Oncefor 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 finishedtime.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 := igo 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语言中文网”:

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

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

  1. java list去除最后一个元素_如何快速删除list中的最后一个元素?

    (前言: 在项目中,在统计在线用户量及其行为方式的时候,想在项目如"/bob/recode/online",结果发现:把写日志的东西放到了ebin文件下,即:/bob/ebin/r ...

  2. python 在set里随机选一个元素_阿博Python之路-详解Set数据类型

    阿博之前分享了Python的Dictonary数据类型,今天来分享Set数据类型. 注意:阿博的开发环境是Python3. Set(集合)简介 Set数据类型,一般我们称为集合,是一个无序不重复的元素 ...

  3. java list取最后一个元素_Java stream() 获取List指定元素或最后一个元素的方法

    示例ListList list = Arrays.asList(1, 2, 3, 4, 5); 1.通过Stream()来获取 如果过滤器的计算结果为true,则检索该元素,否则返回最后一个元素.in ...

  4. dubbo日志关闭_不可忽视的Dubbo线程池避坑指南

    推荐阅读: 阿里巴巴4面Java岗位:算法+性能调优+并发+多线程+数据库 Dubbo+Kafka+MyBatis+reids+Spring+多线程等,学完就去面试BAT 问题描述 线上突然出现Dub ...

  5. java 取栈顶元素_《Java实战之内存模型》详解篇

    内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行 JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的高效稳定运行 不同的JV ...

  6. xpath取最后一个元素

    取xpath最后一个book元素 book[last()] 取xpath最后第二个book元素 book[last()-1] 转载于:https://www.cnblogs.com/z-x-y/p/9 ...

  7. c语言如何删除数组中的某一个元素_数据结构之线性表高效删除重复元素

    刚刚学完数据结构之线性表中关于顺序表和单链表的知识,我们知道顺序表中存储数据的结构是一个数组,对于数组来说,在尾部插入.删除元素是比较高效的,但是如果在中间或者开头插入.删除元素,就会涉及数据的搬移, ...

  8. 数组取10个元素_不知道取什么样的英文名,看看老外最喜欢取的10个男孩和女孩名...

    Emma和Liam分别是2017年最受欢迎的女孩子和男孩子的名字,过去10年间它们逐渐变得流行起来,但是从加利福尼亚州的海岸到弗吉尼亚州的山脉,它们的流行趋势却根据地区大不相同. 下面几张图显示的是美 ...

  9. jquery去掉数组最后一个元素_从数组中删除最后一项

    慕标琳琳 您可以使用以下.slice()方法执行此操作:arr.slice(0, -1);    // returns [1,0]这是一个演示:var arr = [1, 0, 2];var newA ...

最新文章

  1. 在Cisco路由器中配置DHCP服务器
  2. hdu 4722(记忆化搜索)
  3. Mongodb主从配置
  4. Atom编辑Markdown文件保存后行尾的空格自动消失的问题解决
  5. 如何利用python自动化办公项目_python办公自动化:自动进行word文档处理和排版
  6. 磊哥私藏书单分享,160买400的书!
  7. Mybatis plus 整合springboot 出现的Invalid bound statement (not found)问题
  8. 【NOI2019模拟2019.7.4】朝夕相处 (动态规划+BM)
  9. ubuntu 9.10学习笔记
  10. 香港地区Airbnb数据可视化分析
  11. nRF5340开发指南目录汇总
  12. php函数阅读,[PHP源码阅读]strtolower和strtoupper函数
  13. Win11打印机无法打印怎么办?Win11打印机无法打印解决方法
  14. 华为2019秋招面试问答题!(附带笔试参考题)
  15. 23,verilog之参数parameter介绍
  16. 前端实现图片或者视频下载 vue
  17. osmocom-bb 国外的一个开源项目, c118
  18. 《博客创作帮助 - CSDN编辑器测评》
  19. 智能手机战火连连,多年的纷争将何时终结?
  20. fetch下载文件--统一拦截导出文件

热门文章

  1. .NetCore之下载文件
  2. [翻译]在 .NET Core 中的并发编程
  3. 康威定律和系统设计——《微服务设计》读书笔记
  4. Cactus在jexus上安装
  5. IIS负载均衡-Application Request Route详解第三篇:使用ARR进行Http请求的负载均衡
  6. [转]面试官,不要再问我三次握手和四次挥手
  7. Git之回退已经提交到远程仓库的代码(已经push的代码)
  8. 链表之判断一个链表是否为回文结构(二)
  9. 数据库平时错误和使用经验的总结
  10. java ssh 和mvc_[转]JAVA三大框架SSH和MVC