go 关键字之 range
# 一、基本介绍
range
是go提供的一种遍历方式,可以遍历字符串、数组、切片、map、channel等;- 根据被遍历对象的不同,所有的
range
遍历都会在cmd/compile/internal/gc.walkrange
文件中转换为简单的格式;
# 二、string
- 对于
for v1, v2 = range a
字符串的遍历,会转换为以下形式:
ha := a
for hv1 := 0; hv1 < len(ha); {
hv1t := hv1
hv2 := rune(ha[hv1])
if hv2 < utf8.RuneSelf {
hv1++
} else {
hv2, hv1 = decoderune(ha, hv1)
}
v1, v2 = hv1t, hv2
// original body
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 先对字符串 a 进行浅拷贝,获取长度后字符对其字符进行遍历;
# 三、array or slice
- 对于
for v1, v2 := range a
数组或切片的遍历,会被转换为以下形式:
ha := a
hv1 := 0
hn := len(ha)
v1 := hv1
v2 := nil
for ; hv1 < hn; hv1++ {
tmp := ha[hv1]
v1, v2 = hv1, tmp
...
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 先对切片 a 进行浅拷贝,获取长度后对齐进行遍历,ha 是原切片 a 的副本,在遍历前就已经确定了 a 的元素和长度;
- 在遍历期间,v2 每次遍历都是对原元素的一次拷贝,如果元素是一个很大的结构体或字符串,可能会有性能问题;
- 如果在遍历期间如果 a 的长度发生改变或是发生扩容,不影响遍历的结果;如果是在遍历过程中,修改了切片 a 自身的元素,会影响遍历结果;
# 四、map
- 对于
for v1, v2 = range a
哈希表的遍历,会转换为以下形式:
ha := a
hit := hiter(n.Type)
th := hit.Type
mapiterinit(typename(t), ha, &hit)
for ; hit.key != nil; mapiternext(&hit) {
key := *hit.key
val := *hit.val
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 其中
mapiterinit
函数用于初始化迭代器,mapiternext
用于迭代器的遍历,详见go 容器之 map (opens new window); - 迭代器对map的遍历是随机从某个桶的某个元素开始的,所以每次对map的遍历顺序是不一样的;
- 如果在迭代过程中,有其他的 goroutine 正在对map进行写,就会直接抛出错误;
- 即使map是在扩容过程中,也不影响迭代器对map的迭代;
# 五、channel
- 对于
for v1, v2 = range a
管道的遍历,会转换为以下形式:
ha := a
hv1, hb := <-ha
for ; hb != false; hv1, hb = <-ha {
v1 := hv1
hv1 = nil
...
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 如果管道中没有值,或为 nil,那么
hv1, hb = <-ha
操作将会被阻塞; - 如果管道中有值,那么 hb 为 true, hv1 为实际读出来的值;
- 如果管道已经关闭了,那么 hb 为 false, hv1 为类型零值;
- 详细内容可以见go 并发之 chan (opens new window);
编辑 (opens new window)
上次更新: 2024/03/11, 21:40:13