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


相关文章
|
6天前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
29 13
|
2天前
|
测试技术 Go
go语言中测试工具
【10月更文挑战第22天】
10 4
|
2天前
|
SQL 关系型数据库 MySQL
go语言中数据库操作
【10月更文挑战第22天】
12 4
|
2天前
|
缓存 前端开发 中间件
go语言中Web框架
【10月更文挑战第22天】
13 4
|
5天前
|
Go
go语言的复数常量
【10月更文挑战第21天】
18 6
|
5天前
|
Go
go语言的浮点型常量
【10月更文挑战第21天】
13 4
|
5天前
|
编译器 Go
go语言的整型常量
【10月更文挑战第21天】
16 3
|
5天前
|
Serverless Go
Go语言中的并发编程:从入门到精通
本文将深入探讨Go语言中并发编程的核心概念和实践,包括goroutine、channel以及sync包等。通过实例演示如何利用这些工具实现高效的并发处理,同时避免常见的陷阱和错误。
|
6天前
|
安全 Go 开发者
代码之美:Go语言并发编程的优雅实现与案例分析
【10月更文挑战第28天】Go语言自2009年发布以来,凭借简洁的语法、高效的性能和原生的并发支持,赢得了众多开发者的青睐。本文通过两个案例,分别展示了如何使用goroutine和channel实现并发下载网页和构建并发Web服务器,深入探讨了Go语言并发编程的优雅实现。
18 2
|
2天前
|
安全 测试技术 Go
Go语言中的并发编程模型解析####
在当今的软件开发领域,高效的并发处理能力是提升系统性能的关键。本文深入探讨了Go语言独特的并发编程模型——goroutines和channels,通过实例解析其工作原理、优势及最佳实践,旨在为开发者提供实用的Go语言并发编程指南。 ####