品牌网站设计打造,系统页面模板,深圳宝安区医院,在虚拟机里面安装wordpress今年做个 Dig101 系列#xff0c;挖一挖技术背后的故事。Dig101: dig more, simplified more and know moregolang 常用的遍历方式#xff0c;有两种#xff1a;for 和 for-range。而 for-range 使用中有些坑常会遇到#xff0c;今天我们一起来捋一捋。文章目录0x01 遍历取… 今年做个 Dig101 系列挖一挖技术背后的故事。Dig101: dig more, simplified more and know moregolang 常用的遍历方式有两种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 循环初始化会拷贝带遍历的列表(如 arrayslicemap)然后每次遍历的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// }那么怎么改有两种使用局部变量拷贝vfor _, 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 的某个组合如 012210.. 结果是 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