Go 高階指南一文搞懂 range 實現原理

range 是 Go 語言用來遍歷的一種方式,它可以操作數組、切片、map、channel 等。

老規矩,我們先來答幾道題試試水。

答題環節

  1. 遍歷切片:下面程序上有沒有可優化的空間?
func rangeTest(slice []int) {
  for index, value := range slice {
    _, _ = index, value
  }
}

解析:使用 range 遍歷,每次迭代會對 index,value 進行賦值,若數據很大或 value 類型爲 string 時,對 value 的賦值操作可以進行優化,即忽略 value 值,使用 slice[index] 來獲取 value 的值。

  1. 動態遍歷:下面程序上能否正常結束?
func main() {
  v := []int{1,2,3}
  for i := range v {
    v = append(v, i)
  }
}

解析:會正常結束。循環內再改變切片的長度,不影響循環次數,循環次數在循環開始前就已經是確定了的。

  1. 遍歷 Map:下面程序上有沒有可優化的空間?
func rangeTest(mapTest map[int]string) {
  for key, _ := range mapTest {
    _, _ = key, mapTest[key]
  }
}

解析:使用 range 遍歷,根據第一題經驗,我們根據 key 值來獲取 value 的值,看似減少了一次賦值,但使用 mapTest[key] 來獲取 value 值的性能消耗可能高於賦值消耗。能否優化取決於 map 所存儲數據結構特徵,應結合實際情況進行。

實現原理

對於 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 類型。

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 基本一致。
由於循環開始前循環次數就已經確定了,所以循環過程中新添加的元素是無法遍歷到的。

range for 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 表實現的。
插入數據位置是隨機的,所以遍歷過程中新插入的數據不能保證遍歷到。

range for channel

// The loop we generate:
// for {
//     index_temp, ok_temp = <-range
//     if !ok_temp {
//       break
//     }
//     index = index_temp
//     original body
// }

channel 遍歷是依次從 channel 中讀取數據,讀取前是不知道里面有多少個元素的。如果 channel 中沒有元素,則會阻塞等待,如果 channel 已被關閉,則會解除阻塞並退出循環。

注意:

總結

 有什麼問題,可以公衆號內回覆或加我微信交流。

微信公衆號

gophpython

我的微信

wucs_dd

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/QX90GjakU6-uQmdmSCVG5w