• range语句


    1.前言

    range 是Go语言提供的一种遍历手段,可以操作的类型有Array, Slice, Map, Channel.

    2.常见的使用技巧

    1. 遍历切片时使用正确的姿势是通过索引取值

      BadGood
      
      func RangeSlice(slice []int) {
          // 遍历时,其实index不需要,要的是value
          for index, value := range slice {
          _, v := index, value
          }
      }
      
      
      func RangeSlice(slice []int) {
          // 遍历时,其实index不需要,要的是value
          for i:= range slice {
              v := slice[i]
          }
      }
      

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

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

    2. 正确的遍历Map, 和slice刚好不同,不建议在遍历map的时候通过key取出value的值

      BadGood
      
      func RangeMap(myMap map[int]string) {
          // 看似是对少赋了值,实际使用key去value时
          // 性能消耗会更高
          for key, _ := range myMap {
              _, _ = key, myMap[key]
          }
      }
      
      
      func RangeMap(myMap map[int]string) {
        
          for key, value := range myMap {
              _,_:=key,value
          }
      }
      
      能否优化取决于map所存储数据结构特征、结合实际情况进行。
    3. 动态遍历,所谓动态遍历就是说,在遍历目标类型时,类型自身也在改变。

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

      可以正常结束。 许婚内改变切片的长度,不影响循环的次数,循环次数在循环开始前就已经确定了。

    3. 实现原理

    对于for...range语句的实现,编译器的源码gofrontend/go/statements.cc/For_range_statement::do_lower() 方法有如下解释。
    go // 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 // }

    而针对每一种可遍历的数据类型定义了不同的实现。

    3.1 range for slice

    1. 源码中range 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的长度len_temp作为循环次数
      • 循环体中,每次循环会先获取元素值,如果for-range中接收index和value的话,则会对index和value进行一次赋值

    由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是没办法遍历到的。

    3.2 range for Map

    1. 源码中注释如下

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

    map遍历是随机的,导致动态追加的key-value并不能保证能被遍历到

    3.3 range for channel

    1. 遍历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. 编程技巧

    1. 遍历过程中视情况放弃和接收index和value,可以一定程度提升性能
    2. 遍历channel时, 如果channel中没有数据,可能会阻塞,直到关闭channel
    3. 尽量避免遍历过程中修改原数据(动态遍历)
    4. 使用index,value接收range返回值会发生一次数据拷贝
    ♥永远年轻,永远热泪盈眶♥
  • 相关阅读:
    MyBatis与spring面试题-转载
    122. 买卖股票的最佳时机 II(贪心策略)
    121. 买卖股票的最佳时机
    120. 三角形最小路径和
    236. 二叉树的最近公共祖先(快手面试)
    b,b+树区别
    119. 杨辉三角 II
    118. 杨辉三角
    检查型异常(Checked Exception)与非检查型异常(Unchecked Exception)
    Redis
  • 原文地址:https://www.cnblogs.com/failymao/p/14939539.html
Copyright © 2020-2023  润新知