Golang | 关于 for range 的一些细节
source link: https://ijayer.github.io/post/tech/code/golang/20180328-%E5%85%B3%E4%BA%8E_for-range_%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9D%91/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
for range
的迭代变量会被重用range expression
的副本参与 iteration
1. iteration variable 重用
for range
的 idiomatic(惯用)使用方式是使用 short variable declaration(:=) 形式在 for expression 中声明 iteration variable,但需要注意的是这些variable在每次循环体中都会被重用,而不是重新声明。
|
Note: 在上面的代码中,for循环启动的各个goroutine输出的 i, v 值都是 for range 循环结束后 i, v 的最终值,而不是各个goroutine启动时的 i, v 值。 一个可行的 fix 的方法:
|
2. range expression 副本参与 iteration
range 后面接收的表达式的类型包括:array、point to array、slice、map、string、channel(有读权限的)
2.1. array
|
Note: 我们原以为在第一次 iteration,也就是 i=0 时,我们对a的修改(arr[1]=12, arr[2]=13)会在第二次,第三次循环中被 v 取出,但结果却是v取出的依旧是a被修改前的值:2和3。这就是 for…range 的一个不大不小的坑: range expression 副本参与循环。也就是说在上面这个例子里。正真参与循环的是
arr 的副本
,而不是正真的 arr。
Go 中的数组在内部表示为连续的字节序列,虽然长度是Go数组类型的一部分,但长度并不包含在数据的内部表示的部分中,而是由编译器在编译期计算出来。 这个例子中,对range表达式的拷贝,即对一个数据的拷贝,arr’ 则是Go临时分配的连续的字节序列,与 arr 完全不是一块内存。因此,无论 arr 被如何修改,其副本arr’ 依旧保持原值,并且参与循环的是arr’。所以,v从arr’中取出的仍旧是arr的原值,而非修改后的值。
Note: 但是在 for i:=0; i < len(arr); i++ {} 这类循环结构中,直接操作的是原数据的值,并不是一份拷贝,是可以修改数据值的。
2.2. pointer to array
|
Note: 我们看到这次 r 数组的值与最终a被修改后的值一致了。这个例子中使用了 *[5]int 作为 range 的表达式,其副本依旧是一个指向原始数组 arr 的指针, 因此后续所有循环中均是 &arr 指向的的原始数组的指针参与计算,因此 v 能从 &arr 指向的原始数组中取出 arr 修改后的值。
idiomatic go 建议我们尽可能的使用slice替换掉array的使用
2.3. slice
|
Note: 这里slice实现了预期的要求。 那 slice是如何做到的呢?
slice
在 go 的内部表示为一个struct
, 由 (*T, len, cap) 组成,其中 *T 指向slice对应的 underlying(底层) array的指针,len是slice当前的长度,cap是slice的最大容量。当range进行expression复制时,它实际上复制的是一个 slice, 也就是那个 struct。副本 struct 中的 *T 依旧指向原 slice 对应的 array,为此对slice的修改都反映到 underlying array arr上去了,v 从副本struct中 *T 指向的 underlying array 中获取数组元素,也就得到了修改后的元素值。
slice 与 array还有一个不同点:就是 slice 的len在运行时可以被改变,而array得len是一个常量,不可改变。那么len变化的 slice 对 for range 有何影响呢?
|
Note: 在这个例子中,原slice a在 for range过程中被附加了两个元素6和7, 其中,len有5增加到了7,但是对于r却没有产生影响。是因为在 a 的副本 a’ 的内部表示的 struct 中,len 字段并没有改变,依旧是 5。因此 for…range只会循环5次,也就只获取a对应的underlying数组的前5个元素。
range 副本行为会带来一些性能上的消耗,尤其是当range expression的类型的为数组时,range需要复制整个数组;而当 range expression 类型为 pointer to array 或 slice时,这个消耗将小得多,仅仅需要复制一个指针或一个slice的内部表示(一个struct即可)。
2.4. 其他 range expression 类型
对于 range 后面的其他表达式类型,比如 string,map,channel
,for range依旧会创建副本参与计算
string
对 string 来说,由于 string 的内部表示为 struct {*byte, len}, 并且 string 本身是 immutable(一成不变的),因此其行为和消耗和 slice expression类似。不过 for…range 对于 string 来说,每次循环的单位是 rune(code point的值),而不是byte,index为迭代字符码点的第一个字节的 position:
|
对于 map
来说,map 内部表示为一个指针,指针副本也指向真实的map, 因此for range操作均操作的是源map
|
Note:
- 如果map中的某项在循环到达前被在循环体中删除了,那么它
可能
不会被iteration variable获取到- 如果在循环体中新建一个map元素项,那该项元素可能出现在后续循环中,也可能不出现
channel
对于 channel 来说,channel 内部表示为一个指针,channel的指针副本其实指向真是的channel
for…range最终以阻塞读的方式阻塞在channel expression上(即便是buffered channle,当channel中无数据时,for…range也会阻塞在channel),直到channel关闭
|
Note: channel变量为 nil, 则for…range将永远阻塞
See Also
Thanks to the authors 🙂
Recommend
-
83
文件下载的一些安全小细节 Original...
-
86
前言一个人不会两次掉进同一个坑里,但是如果他(她)忘记了坑的位置,那就不一定了。 这篇文章记录了最近使用Golang处理JSON遇到的一些坑。 坑1号坑:omitempty的行为C#中最常用的JSON序列化类库Newtonsoft.Json中,把一个类的实例序列化成JSON,如果我们不想让某...
-
70
Quickdraw的CNN-RNN模型 "猜画小歌"用到的quickdraw模型本质上是一个分类模型,输入是笔画的点的坐标信息和每笔起始的标识信息,应用几个级联的一维卷积,再使用 BiLSTM 层并对结果进行求和,最后使用Softmax层进行分类。...
-
64
概述 Go语言中,struct是一个非常重要的概念,它既是一种数据类型,也可以结合方法(一种特殊的函数),构建类似于OOP的类。不多说,先上 代码 : packag...
-
35
说明 在学习Netty的时候,ByteBuf随处可见,但是如何高效分配ByteBuf还是很复杂的,Netty的池化内存分配这块还是比较难的,很多人学习过,看过但是还是云里雾里的,本篇文章就是主要来讲解: Netty分配池化的堆外内存...
-
45
前言 在一些Java的项目中,有 Maven等这些版本管理工具,可以很好的管理各种版本依赖关系,但是在 Golang 的项目中,之前官方并没有提供版本管理工具,以前都是用 go get 进行安装, 随着项目的变大, 就处理这种...
-
37
小贴士: 本篇文章算是回答一些同学的提问,以MySQL 5.7为例。 我们都知道 MySQL 的 InnoDB 存储引擎是支...
-
8
@State 基础 在 SwiftUI 中,我们使用 @State 进行私有状态管理,并驱动 View 的显示,这是基础中的基础。比如,下面的 ContentView 将在点击加号按钮时将显示的数字 +1:
-
2
本文首发于这里 由于笔者水平有限,文章中难免出现各种错误,欢迎吐槽。 由于篇幅所限,大部分代码细节并未讲的很清楚,如有疑问欢迎讨论。
-
4
ahfuzhang 旧博客:https://ahfuzhang.blogspot.com/ 公众号:一本正经的瞎扯
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK