go 切片取最后一个元素_深挖 Go 之 forrange 排坑指南
Dig101: dig more, simplified more and know more
golang 常用的遍歷方式,有兩種:for 和 for-range。而 for-range 使用中有些坑常會遇到,今天我們一起來捋一捋。
文章目錄
0x01 遍歷取不到所有元素指針?
0x02 遍歷會停止么?
0x03 對大數組這樣遍歷有啥問題?
0x04 對大數組這樣重置效率高么?
0x05 對 map 遍歷時刪除元素能遍歷到么?
0x06 對 map 遍歷時新增元素能遍歷到么?
0x07 這樣遍歷中起 goroutine 可以么?
0x01 遍歷取不到所有元素指針?
如下代碼想從數組遍歷獲取一個指針元素切片集合arr := [2]int{1, 2}res := []*int{}
for _, v := range arr {
res = append(res, &v)
}
//expect: 1 2
fmt.Println(*res[0],*res[1])
//but output: 2 2
答案是【取不到】 同樣代碼對切片[]int{1, 2}或map[int]int{1:1, 2:2}遍歷也不符合預期。問題出在哪里?
通過查看go 編譯源碼[1]可以了解到, for-range 其實是語法糖,內部調用還是 for 循環,初始化會拷貝帶遍歷的列表(如 array,slice,map),然后每次遍歷的v都是對同一個元素的遍歷賦值。也就是說如果直接對v取地址,最終只會拿到一個地址,而對應的值就是最后遍歷的那個元素所附給v的值。對應偽代碼如下:
// len_temp := len(range)// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
那么怎么改?有兩種
- 使用局部變量拷貝v
//局部變量v替換了v,也可用別的局部變量名
v := v
res = append(res, &v)
}
- 直接索引獲取原來的元素
for k := range arr {
res = append(res, &arr[k])
}
理順了這個問題后邊的坑基本都好發現了,來迅速過一遍
0x02 遍歷會停止么?
v := []int{1, 2, 3}for i := range v {
v = append(v, i)
}
答案是【會】,因為遍歷前對v做了拷貝,所以期間對原來v的修改不會反映到遍歷中
0x03 對大數組這樣遍歷有啥問題?
//假設值都為1,這里只賦值3個var arr = [102400]int{1, 1, 1}
for i, n := range arr {
//just ignore i and n for simplify the example
_ = i
_ = n
}
答案是【有問題】!遍歷前的拷貝對內存是極大浪費啊 怎么優化?有兩種
- 對數組取地址遍歷for i, n := range &arr
- 對數組做切片引用for i, n := range arr[:]
反思題:對大量元素的 slice 和 map 遍歷為啥不會有內存浪費問題?(提示,底層數據結構是否被拷貝)
0x04 對大數組這樣重置效率高么?
//假設值都為1,這里只賦值3個var arr = [102400]int{1, 1, 1}
for i, _ := range &arr {
arr[i] = 0
}
答案是【高】,這個要理解得知道 go 對這種重置元素值為默認值的遍歷是有優化的, 詳見go 源碼:memclrrange[2]
// Lower n into runtime·memclr if possible, for// fast zeroing of slices and arrays (issue 5373).
// Look for instances of
//
// for i := range a {
// a[i] = zero
// }
//
// in which the evaluation of a is side-effect-free.
0x05 對 map 遍歷時刪除元素能遍歷到么?
var m = map[int]int{1: 1, 2: 2, 3: 3}//only del key once, and not del the current iteration key
var o sync.Once
for i := range m {
o.Do(func() {
for _, key := range []int{1, 2, 3} {
if key != i {
fmt.Printf("when iteration key %d, del key %d\n", i, key)
delete(m, key)
break
}
}
})
fmt.Printf("%d%d ", i, m[i])
}
答案是【不會】 map 內部實現是一個鏈式 hash 表,為保證每次無序,初始化時會隨機一個遍歷開始的位置[3], 這樣,如果刪除的元素開始沒被遍歷到(上邊once.Do函數內保證第一次執行時刪除未遍歷的一個元素),那就后邊就不會出現。
0x06 對 map 遍歷時新增元素能遍歷到么?
var m = map[int]int{1:1, 2:2, 3:3}for i, _ := range m {
m[4] = 4
fmt.Printf("%d%d ", i, m[i])
}
答案是【可能會】,輸出中可能會有44。原因同上一個, 可以用以下代碼驗證
var createElemDuringIterMap = func() {var m = map[int]int{1: 1, 2: 2, 3: 3}
for i := range m {
m[4] = 4
fmt.Printf("%d%d ", i, m[i])
}
}
for i := 0; i < 50; i++ {
//some line will not show 44, some line will
createElemDuringIterMap()
fmt.Println()
}
0x07 這樣遍歷中起 goroutine 可以么?
var m = []int{1, 2, 3}for i := range m {
go func() {
fmt.Print(i)
}()
}
//block main 1ms to wait goroutine finished
time.Sleep(time.Millisecond)
答案是【不可以】。預期輸出 0,1,2 的某個組合,如 012,210.. 結果是 222. 同樣是拷貝的問題 怎么解決
- 以參數方式傳入
go func(i int) {
fmt.Print(i)
}(i)
}
- 使用局部變量拷貝
i := i
go func() {
fmt.Print(i)
}()
}
發現沒,一個簡單的 for-range,仔細剖析下來也是有不少有趣的地方。希望剖析后能讓你更進一步的了解。如有問題歡迎留言交流。
See more:Go Range Loop Internals[4],Common Mistakes[5],go101: Arrays, Slices and Maps in Go[6]
推薦閱讀
深入 Go 內存分配超級棒的文章:Go 內存分配器可視化指南
喜歡本文的朋友,歡迎關注“Go語言中文網”:
Go語言中文網啟用微信學習交流群,歡迎加微信:274768166
參考資料
[1]go編譯源碼: https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5501
[2]go源碼:memclrrange: https://github.com/golang/go/blob/ea020ff3de9482726ce7019ac43c1d301ce5e3de/src/cmd/compile/internal/gc/range.go#L363
[3]隨機一個遍歷開始的位置: https://github.com/golang/go/blob/0bd3853512ea0dcb252ce02113d3929db03d6aa6/src/runtime/map.go#L826
[4]Go Range Loop Internals: https://garbagecollected.org/2017/02/22/go-range-loop-internals/
[5]Common Mistakes: https://github.com/golang/go/wiki/CommonMistakes
[6]go101: Arrays, Slices and Maps in Go: https://go101.org/article/container.html
總結
以上是生活随笔為你收集整理的go 切片取最后一个元素_深挖 Go 之 forrange 排坑指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mac 启动mysql多实例_实践:my
- 下一篇: BT樱桃磁力搜索攻略,100%找到准确结