一. 基础问题
- 示例问题: 如下程序是否能正常结束
- main()函数中定义一个切片v,通过range遍历v,遍历过程中不断向v中添加新的元素
- 能够正常结束。循环内改变切片的长度,不影响循环次数,循环次效在循环开始前就已经确定了
func main() {
v := []int{1, 2, 3}
for i:= range v {
v = append(v, i)
}
}
- 示例问题2: 请问性能上有没有可优化的空间:
//遍历过程中每次迭代会对index和value进行赋值,
//如果数据量大或者value类型为string时,
//对value的赋值操作可能是多余的,可以在for-range中忽略value值,
//推荐使用slice[index]引用value值
func RangeSlice(slice []int) {
for index, value := range slice {
_, _ = index, value
}
}
//函数中for-range语句中只获取key值,然后跟据key值获取value值,
//虽然看似减少了一次赋值,但通过key值查找value值的性能消耗可能高于赋值消耗。
//能否优化取决于map所存储数据结构特征、结合实际情况进行
func RangeMap(myMap map[int]string) {
for key, _ := range myMap {
_, _ = key, myMap[key]
}
}
二. 实现原理
- range支持数组、数组指针、切片、map和channel类型,对于不同类型有些细节上的差异
1. 遍历数组指针或slice
- 遍历slice前会先获以slice的长度len_temp作为循环次数,循环体中,每次循环会先获取元素值,如果for-range中接收index和value的话,则会对index和value进行一次赋值。由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是没办法遍历到的。另外,数组与数组指针的遍历过程与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
// }
2. 遍历map
- 遍历map时没有指定循环次数,循环体与遍历slice类似。由于map底层实现与slice不同,map底层使用hash表实现,插入数据位置是随机的,所以遍历过程中新插入的数据不能保证遍历到
// 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
// }
3. 遍历channel
- channel遍历是依次从channel中读取数据,读取前是不知道里面有多少个元素的。如果channel中没有元素,则会阻塞等待,如果channel已被关闭,则会解除阻塞并退出循环
// The loop we generate:
// for {
// index_temp, ok_temp = <-range
// if !ok_temp {
// break
// }
// index = index_temp
// original body
// }
- 注意
- 上述注释中index_temp实际上描述是有误的,应该为value_temp,因为index对于channel是没有意义的。
- 使用for-range遍历channel时只能获取一个返回值。