专栏首页Go语言指北Go高阶指南10,一文搞懂 range 实现原理

Go高阶指南10,一文搞懂 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 返回值会发生一次数据拷贝

有什么问题,可以公众号内回复或加我微信交流。

本文分享自微信公众号 - 微客鸟窝(gophpython),作者:有码无尘

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Go高阶指南07,一文搞懂 defer 实现原理

    defer 语句用于延迟函数的调用,使用 defer 关键字修饰一个函数,会将这个函数压入栈中,当函数返回时,再把栈中函数取出执行。

    微客鸟窝
  • Go高阶指南02,slice 实现原理

    slice 切片,因为其可以方便的进行扩容、传递等,在实际应用中比数组更加灵活。切片的一些基础使用可以看下之前的文章,传送门。

    微客鸟窝
  • Go高阶指南04,struct 实现原理

    Go 语言中没有像类的概念,但是可以通过结构体 struct实现面向对象编程。struct 结构体的一些基础使用可以看下之前的文章--结构体和接口的使用,传送门...

    微客鸟窝
  • Go高阶指南05,iota 实现原理

    我们知道,iota 常用于 const 表达式中,它的值是从 0 开始,每增加一行,iota 值 +1。

    微客鸟窝
  • Go高阶指南06,string 实现原理

    源代码中 src/builtin/builtin.go string 的描述如下:

    微客鸟窝
  • Go高阶指南09,select 实现原理

    select 是 GO 语言中用来提供 IO 复用的机制,它可以检测多个 chan 是否 ready(可读/可写)。

    微客鸟窝
  • 适合 Go 新手学习的开源项目——在 GitHub 学编程

    故事要从 2007 年说起。因为受够了 C++ 煎熬的 Google 首席软件工程师 Rob Pike 召集 Robert Griesemer 和 Ken Th...

    HelloGitHub
  • 聊聊阿秀过去三年间做的最正确的一件事 | 快来薅羊毛

    我买的大部分是技术书,也有一些非技术书,比如《明朝那些事儿》、《平凡的世界》之类的。

    拓跋阿秀
  • Go 语言学习路线来啦

    时不时的有人问我一些关于 Go 语言学习路线、学习资源方面的问题,这篇文章就来详细说一说。借此希望给那些正在学习,或是想学习 Go 语言的朋友一些帮助。

    roseduan
  • Go语言学习计划和一些总结

    bool型、数字类型、字符串类型、派生类型(指针、数组、结构化、channel、函数、切片、接口、Map等)

    AsiaYe
  • Github开源免费编程书籍

    时见疏星
  • 很多小伙伴问我推荐什么书籍和网课,这次把私藏很久的资料都贡献了(上)

    平时有不少读者朋友问,有没有学习书籍网上课程推荐?今天结合自己学习经历与身边几个朋友的经历总结了一份程序员相关的书籍和网课。

    C语言与CPP编程
  • Go 语言调度(三): 并发

    当我解决问题时,尤其是新问题,我不会一上来就想着能不能使用并发来处理。我会首先想出一个顺序执行的解决方案。然后在可读性和技术评审后,我会开始考虑并发处理能不能让...

    李海彬
  • 每个JavaScript工程师都应懂的33个概念

    这个项目是为了帮助开发者掌握 JavaScript 概念而创立的。它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南。

    Fundebug
  • 每个JavaScript工程师都应懂的33个概念

    这个项目是为了帮助开发者掌握 JavaScript 概念而创立的。它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南。

    Fundebug
  • 2019 最新 Java 核心技术教程,都在这了!

    以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新。

    Java技术栈
  • 最新 Java 核心技术教程,都在这了!

    以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,2021最新更新版,本文会长期更新。

    Java技术栈
  • 一些免费的学习资源 原

    HTML5 Canvas编程:http://blog.csdn.net/column/details/canvas-programming.html GTK编...

    wuweixiang
  • 前端吐血推荐的必读书籍

    自古以来就有书中自有黄金屋,书中自有颜如玉。前阵子当当搞活动,买书满 200 减 100,但是很多同学给我留言让我推荐书籍,当时是真没空,现在有空了把我看过的一...

    桃翁

扫码关注云+社区

领取腾讯云代金券