Go 高階指南一文搞懂 range 實現原理
range 是 Go 語言用來遍歷的一種方式,它可以操作數組、切片、map、channel 等。
老規矩,我們先來答幾道題試試水。
答題環節
- 遍歷切片:下面程序上有沒有可優化的空間?
func rangeTest(slice []int) {
for index, value := range slice {
_, _ = index, value
}
}
解析:使用 range 遍歷,每次迭代會對 index,value 進行賦值,若數據很大或 value 類型爲 string 時,對 value 的賦值操作可以進行優化,即忽略 value 值,使用 slice[index] 來獲取 value 的值。
- 動態遍歷:下面程序上能否正常結束?
func main() {
v := []int{1,2,3}
for i := range v {
v = append(v, i)
}
}
解析:會正常結束。循環內再改變切片的長度,不影響循環次數,循環次數在循環開始前就已經是確定了的。
- 遍歷 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 已被關閉,則會解除阻塞並退出循環。
注意:
-
上述註釋中 index_temp 實際上描述是有誤的,應該爲 value_temp,因爲 index 對於 channel 是沒有意義的。
-
使用 for-range 遍歷 channel 時只能獲取一個返回值。
總結
-
遍歷過程中可以適情況丟棄 index 或 value,可以一定程度上提升性能
-
遍歷 channel 時,如果 channel 中沒有數據,可能會阻塞
-
使用 index,value 接收 range 返回值會發生一次數據拷貝
有什麼問題,可以公衆號內回覆或加我微信交流。
微信公衆號
gophpython
我的微信
wucs_dd
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/QX90GjakU6-uQmdmSCVG5w