前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go高阶指南10,一文搞懂 range 实现原理

Go高阶指南10,一文搞懂 range 实现原理

作者头像
微客鸟窝
发布2021-09-10 15:39:27
2.9K0
发布2021-09-10 15:39:27
举报
文章被收录于专栏:Go语言指北

range 是 Go 语言用来遍历的一种方式,它可以操作数组、切片、map、channel 等。

老规矩,我们先来答几道题试试水。

答题环节

  1. 遍历切片:下面程序上有没有可优化的空间?
代码语言:javascript
复制
func rangeTest(slice []int) {
  for index, value := range slice {
    _, _ = index, value
  }
}

解析:使用 range 遍历,每次迭代会对 index,value 进行赋值,若数据很大或 value 类型为 string 时,对 value 的赋值操作可以进行优化,即忽略 value 值,使用 slice[index] 来获取 value 的值。

  1. 动态遍历:下面程序上能否正常结束?
代码语言:javascript
复制
func main() {
  v := []int{1,2,3}
  for i := range v {
    v = append(v, i)
  }
}

解析:会正常结束。循环内再改变切片的长度,不影响循环次数,循环次数在循环开始前就已经是确定了的。

  1. 遍历Map:下面程序上有没有可优化的空间?
代码语言:javascript
复制
func rangeTest(mapTest map[int]string) {
  for key, _ := range mapTest {
    _, _ = key, mapTest[key]
  }
}

解析:使用 range 遍历,根据第一题经验,我们根据 key 值来获取value 的值,看似减少了一次赋值,但使用 mapTest[key] 来获取 value 值的性能消耗可能高于赋值消耗。能否优化取决于 map 所存储数据结构特征,应结合实际情况进行。

实现原理

对于 for-range 语句的实现,从编译器源码 gofrontend/go/statements.cc/For_range_statement::do_lower() 方法中可以看到有如下注释:

代码语言:javascript
复制
// Arrange to do a loop appropriate for the type. We will produce
// for INIT ; COND ; POST {
//     ITER_INIT
//     INDEX = INDEX_TEMP
//     VALUE = VALUE_TEMP // If there is a value
//     original statements
// }

可见 range 是一个 C 风格的循环结构。range 支持数组、数组指针、切片、map 和 channel 类型。

range for slice

注释解释了遍历slice的过程:

代码语言:javascript
复制
// The loop we generate:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
//     value_temp = for_temp[index_temp]
//     index = index_temp
//     value = value_temp
//     original body
// }

遍历 slice 前会先获取 slice 的长度 len_temp 来作为循环次数,循环体中,每次循环会先获取元素值,如果 for-range 中接收 index和 value 的话,则会对 index 和 value 进行一次赋值。数组与数组指针的遍历过程与 slice 基本一致。 由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是无法遍历到的。

range for map

代码语言:javascript
复制
// The loop we generate:
// 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
// }

遍历 map 时没有指定循环次数,循环体与遍历 slice 类似。由于 map 底层实现与 slice 不同,map 底层使用 hash 表实现的。 插入数据位置是随机的,所以遍历过程中新插入的数据不能保证遍历到。

range for channel

代码语言:javascript
复制
// The loop we generate:
// for {
//     index_temp, ok_temp = <-range
//     if !ok_temp {
//       break
//     }
//     index = index_temp
//     original body
// }

channel 遍历是依次从 channel 中读取数据,读取前是不知道里面有多少个元素的。如果 channel 中没有元素,则会阻塞等待,如果 channel 已被关闭,则会解除阻塞并退出循环。

注意:

  • 上述注释中 index_temp 实际上描述是有误的,应该为 value_temp,因为 index 对于 channel 是没有意义的。
  • 使用 for-range 遍历 channel 时只能获取一个返回值。

总结

  • 遍历过程中可以适情况丢弃 index 或 value,可以一定程度上提升性能
  • 遍历 channel 时,如果 channel 中没有数据,可能会阻塞
  • 使用 index,value 接收 range 返回值会发生一次数据拷贝

有什么问题,可以公众号内回复或加我微信交流。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微客鸟窝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 答题环节
  • 实现原理
    • range for slice
      • range for map
        • range for channel
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档