range是go提供的一种迭代遍历手段,可以遍历数组、切片、Map、channel等,使用频率非常高。
实现原理
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
使用range遍历切片,会生成如下的代码:
for_temp := range
len_temp := len(for_temp)
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = for_temp[idex_temp]
index = index_temp
value = value_temp
original body
}遍历slice前先获取slice的长度len_temp作为循环次数,循环体中,每次循环会先获取元素值,如果for-range中接收index和value的话,则会对index和value进行一次赋值。
由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是没办法遍历到的,例如:
func main() {
v := []int{1,2,3}
for i := range v {
v = append(v, i)
}
}这样的写法是能够正常结束的。
数组和数组指针的遍历过程与slice基本一致。
range for map
遍历map的是过程如下:
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底层实现采用hash表,插入数据的位置是随机的,所以遍历过程中新插入的数据不能保证遍历到。
range for channel
遍历channel最为特殊
for {
value_temp, ok_temp = <-range
if !ok_temp {
break
}
value = value_temp
original body
}channel遍历是依次从channel中读取数据,读取前不知道有多少元素。 如果channel中没有元素,则会阻塞等待,如果channel已被关闭,则会解除阻塞并退出循环。
总结
- 遍历过程可以视情况放弃接受index或value,可以一定程度上提升性能。
- 遍历channel时,如果channel中没有数据,可能会阻塞
- 尽量避免在遍历过程中修改原数据
- for-range的实现实际上是c风格的for循环
- 使用index,value接收range返回值会发生一次数据拷贝。