写点什么

Go 进阶指南,手摸手带你深入了解 range 实现原理

用户头像
微客鸟窝
关注
发布于: 2 小时前
Go进阶指南,手摸手带你深入了解 range 实现原理

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


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

答题环节

  1. 遍历切片:下面程序上有没有可优化的空间?


func rangeTest(slice []int) {  for index, value := range slice {    _, _ = index, value  }}
复制代码


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


  1. 动态遍历:下面程序上能否正常结束?


func main() {  v := []int{1,2,3}  for i := range v {    v = append(v, i)  }}
复制代码


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


  1. 遍历 Map:下面程序上有没有可优化的空间?


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() 方法中可以看到有如下注释:


// 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 的过程:


// 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

// 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

// 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 返回值会发生一次数据拷贝

发布于: 2 小时前阅读数: 2
用户头像

微客鸟窝

关注

还未添加个人签名 2019.11.01 加入

公众号《微客鸟窝》笔者,目前从事web后端开发,涉及语言PHP、golang。获得美国《时代周刊》2006年度风云人物!

评论

发布
暂无评论
Go进阶指南,手摸手带你深入了解 range 实现原理