Go 进阶指南,手摸手带你深入了解 range 实现原理
range 是 Go 语言用来遍历的一种方式,它可以操作数组、切片、map、channel 等。
老规矩,我们先来答几道题试试水。
答题环节
遍历切片:下面程序上有没有可优化的空间?
解析:使用 range 遍历,每次迭代会对 index,value 进行赋值,若数据很大或 value 类型为 string 时,对 value 的赋值操作可以进行优化,即忽略 value 值,使用 slice[index] 来获取 value 的值。
动态遍历:下面程序上能否正常结束?
解析:会正常结束。循环内再改变切片的长度,不影响循环次数,循环次数在循环开始前就已经是确定了的。
遍历 Map:下面程序上有没有可优化的空间?
解析:使用 range 遍历,根据第一题经验,我们根据 key 值来获取 value 的值,看似减少了一次赋值,但使用 mapTest[key] 来获取 value 值的性能消耗可能高于赋值消耗。能否优化取决于 map 所存储数据结构特征,应结合实际情况进行。
实现原理
对于 for-range 语句的实现,从编译器源码 gofrontend/go/statements.cc/For_range_statement::do_lower() 方法中可以看到有如下注释:
可见 range 是一个 C 风格的循环结构。range 支持数组、数组指针、切片、map 和 channel 类型。
range for slice
注释解释了遍历 slice 的过程:
遍历 slice 前会先获取 slice 的长度 len_temp 作为循环次数,循环体中,每次循环会先获取元素值,如果 for-range 中接收 index 和 value 的话,则会对 index 和 value 进行一次赋值。数组与数组指针的遍历过程与 slice 基本一致。
由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是无法遍历到的。
range for map
遍历 map 时没有指定循环次数,循环体与遍历 slice 类似。由于 map 底层实现与 slice 不同,map 底层使用 hash 表实现的。
插入数据位置是随机的,所以遍历过程中新插入的数据不能保证遍历到。
range for channel
channel 遍历是依次从 channel 中读取数据,读取前是不知道里面有多少个元素的。如果 channel 中没有元素,则会阻塞等待,如果 channel 已被关闭,则会解除阻塞并退出循环。
注意:
上述注释中 index_temp 实际上描述是有误的,应该为 value_temp,因为 index 对于 channel 是没有意义的。
使用 for-range 遍历 channel 时只能获取一个返回值。
总结
遍历过程中可以适情况丢弃 index 或 value,可以一定程度上提升性能
遍历 channel 时,如果 channel 中没有数据,可能会阻塞
使用 index,value 接收 range 返回值会发生一次数据拷贝
版权声明: 本文为 InfoQ 作者【微客鸟窝】的原创文章。
原文链接:【http://xie.infoq.cn/article/0de8d8efb0732ade9572c5317】。文章转载请联系作者。
评论