range是Golang提供的一种迭代遍历手段,可操作的类型有数组、切片、Map、channel等,实际使用频率非常高。

探索range的实现机制是很有意思的事情,这可能会改变你使用range的习惯。

2. 热身

按照惯例,我们看几个有意思的题目,用于检测对range的了解程度。

2.1 题目一:切片遍历

下面函数通过遍历切片,打印切片的下标和元素值,请问性能上有没有可优化的空间?

func RangeSlice(slice []int) {for index, value := range slice {_, _ = index, value}
}

程序解释:
函数中使用for-range对切片进行遍历,获取切片的下标和元素素值,这里忽略函数的实际意义。

参考答案:
遍历过程中每次迭代会对index和value进行赋值,如果数据量大或者value类型为string时,对value的赋值操作可能是多余的,可以在for-range中忽略value值,使用slice[index]引用value值。

2.2 题目二:Map遍历

下面函数通过遍历Map,打印Map的key和value,请问性能上有没有可优化的空间?

func RangeMap(myMap map[int]string) {for key, _ := range myMap {_, _ = key, myMap[key]}
}

程序解释:
函数中使用for-range对map进行遍历,获取map的key值,并根据key值获取获取value值,这里忽略函数的实际意义。

参考答案:
函数中for-range语句中只获取key值,然后根据key值获取value值,虽然看似减少了一次赋值,但通过key值查找value值的性能消耗可能高于赋值消耗。能否优化取决于map所存储数据结构特征、结合实际情况进行。

2.3 题目三:动态遍历

请问如下程序是否能正常结束?

func main() {v := []int{1, 2, 3}for i:= range v {v = append(v, i)}
}

程序解释:
main()函数中定义一个切片v,通过range遍历v,遍历过程中不断向v中添加新的元素。

参考答案:
能够正常结束。循环内改变切片的长度,不影响循环次数,循环次数在循环开始前就已经确定了。

3. 实现原理

对于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类型,对于不同类型有些细节上的差异。

3.1 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基本一致,不再赘述。

3.2 range for map

下面的注释解释了遍历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表实现,插入数据位置是随机的,所以遍历过程中新插入的数据不能保证遍历到。

3.3 range for channel

遍历channel是最特殊的,这是由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时只能获取一个返回值。

4. 编程Tips

  • 遍历过程中可以视情况放弃接收index或value,可以一定程度上提升性能
  • 遍历channel时,如果channel中没有数据,可能会阻塞
  • 尽量避免遍历过程中修改原数据

5. 总结

  • for-range的实现实际上是C风格的for循环
  • 使用index,value接收range返回值会发生一次数据拷贝

golang for range原理(转载)相关推荐

  1. Golang sync.Map 原理(两个map实现 读写分离、适用读多写少场景)

    参考: 由浅入深聊聊Golang的sync.Map 通过对源码的逐行分析,清晰易懂 Golang sync.Map原理 通过向 sync.Map 中增删改查来介绍sync.Map的底层原理 Golan ...

  2. Golang的range

    range 是 golang中特别常用的一种遍历方式,走C++入门的看到这样的遍历方式感觉太好用了.但是如果没有认真思考过range的工作原理,在一些特定的场景使用range,可能并不能达到预期的效果 ...

  3. linux+mmap父子通信_linux库函数mmap()原理?转载

    linux库函数mmap()原理 转载 1.mmap基本概念 2.mmap内存映射原理 3.mmap和常规文件操作的区别 4.mmap优点总结 5.mmap相关函数 6.mmap使用细节 7.mmap ...

  4. Golang Context 详细原理和使用技巧

    文章目录 Golang Context 详细原理和使用技巧 Context 背景 和 适用场景 Context 的背景 Context 的功能和目的 Context 的基本使用 Context 的同步 ...

  5. GoLang定时器实现原理

    简介 工作中经常有定时执行某些代码块的需求,如果是PHP代码,一般写个脚本,然后用Cron实现. Go里提供了两种定时器:Timer(到达指定时间触发且只触发一次)和 Ticker(间隔特定时间触发) ...

  6. Golang sync.Map原理

    原生map的"先天不足" 对于已经初始化了的原生map,我们可以尽情地对其进行并发读: package mainimport ("fmt""math/ ...

  7. ConcurrentHashMap实现原理--转载

    原文地址:http://ajax-xu.iteye.com/blog/1104649 ConcurrentHashMap是Java 5中支持高并发.高吞吐量的线程安全HashMap实现.在这之前我对C ...

  8. java原子操作的实现原理--转载

    原文地址:http://www.infoq.com/cn/articles/atomic-operation 1. 引言 原子(atom)本意是"不能被进一步分割的最小粒子",而原 ...

  9. JDK动态代理实现原理--转载

    之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了.  ...

最新文章

  1. 企业级 SpringBoot 教程 (十九) 验证表单信息
  2. golang 数组和切片
  3. C运算符解析及优先级
  4. winform绑定多张图片
  5. Elasticsearch--分布式RESTful搜索引擎
  6. MFC DoDataExchange()绑定技术
  7. Spring框架之演示JDBC的模板类
  8. 机器学习入门:隐马尔科夫模型-8
  9. 计算机流体仿真,ANSYS FLUENT 计算流体力学软件
  10. 运维 之 常用运维工具
  11. 生产排程系统_【PSI系统】在生产中进行更智慧的详细计划:计划排程工具APS是否值得企业投资?...
  12. CentOS通过DNSpod实现DDNS动态域名,在家也可以搭建主机服务器了
  13. Istio 东西向流量管理
  14. Photoshop(PS)CC2020安装教程【64位】
  15. 读书笔记——好句摘抄
  16. 《C Primer Plus》学习笔记—第9章
  17. CAP理论举例及说明
  18. sql获取group by最后一条记录
  19. nico老是显示服务器升级,Nico会员服务条款
  20. 什么是邮箱地址?邮箱地址在哪里找?

热门文章

  1. DDD(领域驱动设计)
  2. Intellij IDEA自定义类注释模板
  3. LabVIEW多列列表框背景颜色操作
  4. 排序 -> 选择排序
  5. oracle 052 题库更新,OCP题库升级,新版052考试题及答案整理-18
  6. 定个小目标,炒股咯....
  7. Battle Encoder Shirase一款能限制进程CPU占有率的小东西
  8. Java 轻量级锁原理详解(Lightweight Locking)
  9. vue练习之vue+cnode api
  10. Atitit.执行cli cmd的原理与调试