likes
comments
collection
share

go 关键字之 range

作者站长头像
站长
· 阅读数 36

个人主页:二郎腿 (erlangtui.top)

一、基本介绍

  • 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
}
  • 先对字符串 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
    ...
}
  • 先对切片 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
}
  • 其中mapiterinit函数用于初始化迭代器,mapiternext用于迭代器的遍历,详见go 容器之 map
  • 迭代器对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
    ...
}
  • 如果管道中没有值,或为 nil,那么hv1, hb = <-ha操作将会被阻塞;
  • 如果管道中有值,那么 hb 为 true, hv1 为实际读出来的值;
  • 如果管道已经关闭了,那么 hb 为 false, hv1 为类型零值
  • 详细内容可以见go 并发之 chan
转载自:https://juejin.cn/post/7375386125814202406
评论
请登录