<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
for range語句是業務開發中編寫頻率很高的程式碼,其中會有一些常見的坑,看完這篇文章會讓你少入坑。
range是Golang提供的一種迭代遍歷手段,可操作的型別有陣列、切片、string、map、channel等
1、遍歷陣列
myArray := [3]int{1, 2, 3} for i, ele := range myArray { fmt.Printf("index:%d,element:%dn", i, ele) fmt.Printf("index:%d,element:%dn", i, myArray[i]) }
直接取元素或通過下標取
2、遍歷slice
mySlice := []string{"I", "am", "peachesTao"} for i, ele := range mySlice { fmt.Printf("index:%d,element:%sn", i, ele) fmt.Printf("index:%d,element:%sn", i, mySlice[i]) }
直接取元素或通過下標取
3、遍歷string
s:="peachesTao" for i,item := range s { fmt.Println(string(item)) fmt.Printf("index:%d,element:%sn", i, string(s[i])) }
直接取元素或通過下標取
注意:迴圈體中string中的元素實際上是byte型別,需要轉換為字面字元
4、遍歷map
myMap := map[int]string{1:"語文",2:"數學",3:"英語"} for key,value := range myMap { fmt.Printf("key:%d,value:%sn", key, value) fmt.Printf("key:%d,value:%sn", key, myMap[key]) }
直接取元素或通過下標取
5、遍歷channel
myChannel := make(chan int) go func() { for i:=0;i<10;i++{ time.Sleep(time.Second) myChannel <- i } }() go func() { for c := range myChannel { fmt.Printf("value:%dn", c) } }()
channel遍歷是迴圈從channel中讀取資料,如果channel中沒有資料,則會阻塞等待,如果channel已被關閉,則會退出迴圈。
for range可以直接存取目標物件中的元素,而for必須通過下標存取
for frange可以存取map、channel物件,而for不可以
下面的例子是將mySlice中每個元素的後面都加上字元"-new"
mySlice := []string{"I", "am", "peachesTao"} for _, ele := range mySlice { ele=ele+"-new" } fmt.Println(mySlice)
結果:
[I am peachesTao]
列印mySlice發現元素並沒有更新,為什麼會這樣?
原因是for range語句會將目標物件中的元素copy一份值的副本,修改副本顯然不能對原元素產生影響
為了證明上述結論,在遍歷前和遍歷中列印出元素的記憶體地址
mySlice := []string{"I", "am", "peachesTao"} fmt.Printf("遍歷前首元素記憶體地址:%pn",&mySlice[0]) for _, ele := range mySlice { ele=ele+"-new" fmt.Printf("遍歷中元素記憶體地址:%pn",&ele) } fmt.Println(mySlice)
結果:
遍歷前第一個元素記憶體地址:0xc000054180
遍歷前第二個元素記憶體地址:0xc000054190
遍歷前第三個元素記憶體地址:0xc0000541a0
遍歷中元素記憶體地址:0xc000010200
遍歷中元素記憶體地址:0xc000010200
遍歷中元素記憶體地址:0xc000010200
[I am peachesTao]
可以得出兩個結論:
比如遍歷mySlice元素生成一個[]*string型別的mySliceNew,要通過一箇中間變數取中間變數的地址(或者通過下標的形式存取元素也可以)加入mySliceNew,如果直接取元素副本的地址會導致mySliceNew中所有元素都是一樣的,如下:
mySlice := []string{"I", "am", "peachesTao"} var mySliceNew []*string for _, item := range mySlice { itemTemp := item mySliceNew = append(mySliceNew, &itemTemp) //mySliceNew = append(mySliceNew, &item) 錯誤的做法 }
回到剛才那個問題,如何能在遍歷中修改元素呢?答案是直接通過下標存取slice中的元素對其賦值,如下:
mySlice := []string{"I", "am", "peachesTao"} for i, _ := range mySlice { mySlice[i] = mySlice[i]+"-new" } fmt.Println(mySlice)
結果:
[I-new am-new peachesTao-new]
可以看到元素已經被修改
我們定義一個結構體Item,包含int型別的id欄位,對結構體陣列分別使用for、for range item、for range index的方式進行遍歷,下面是測試程式碼(直接參照“Go語言高效能程式設計”這篇文章中的例子,下面的reference中有連結地址)
type Item struct { id int } func BenchmarkForStruct(b *testing.B) { var items [1024]Item for i := 0; i < b.N; i++ { length := len(items) var tmp int for k := 0; k < length; k++ { tmp = items[k].id } _ = tmp } } func BenchmarkRangeIndexStruct(b *testing.B) { var items [1024]Item for i := 0; i < b.N; i++ { var tmp int for k := range items { tmp = items[k].id } _ = tmp } } func BenchmarkRangeStruct(b *testing.B) { var items [1024]Item for i := 0; i < b.N; i++ { var tmp int for _, item := range items { tmp = item.id } _ = tmp } }
執行基準測試命令:
go test -bench . test/for_range_performance_test.go
測試結果:
goos: darwin
goarch: amd64
BenchmarkForStruct-4 3176875 375 ns/op
BenchmarkRangeIndexStruct-4 3254553 369 ns/op
BenchmarkRangeStruct-4 3131196 384 ns/op
PASS
ok command-line-arguments 4.775s
可以看出:
for range 通過Index和直接存取元素的方式和for的方式遍歷效能幾乎無差異
下面我們在Item結構體新增一個byte型別長度為4096的陣列欄位val
type Item struct { id int val [4096]byte }
再執行一遍基準測試,結果如下:
goos: darwin
goarch: amd64
BenchmarkForStruct-4 2901506 393 ns/op
BenchmarkRangeIndexStruct-4 3160203 381 ns/op
BenchmarkRangeStruct-4 1088 948678 ns/op
PASS
ok command-line-arguments 4.317s
可以看出:
結論:
對於for-range語句的實現,可以從編譯器原始碼中找到答案。
編譯器原始碼gofrontend/go/statements.cc/For_range_statement::do_lower()【連結見下方
reference】
方法中有如下注釋。
// 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支援string、陣列、陣列指標、切片、map和channel型別,對於不同型別有些細節上的差異。
1、range for slice
下面的註釋解釋了遍歷slice的過程:
For_range_statement::lower_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前會先獲得slice的長度len_temp作為迴圈次數,迴圈體中,每次迴圈會先獲取元素值,如果for-range中接收index和value的話,則會對index和value進行一次賦值,這就解釋了對大元素進行遍歷會影響效能,因為大物件賦值會產生gc
由於迴圈開始前回圈次數就已經確定了,所以迴圈過程中新新增的元素是沒辦法遍歷到的。
另外,陣列與陣列指標的遍歷過程與slice基本一致,不再贅述。
2、range for map
下面的註釋解釋了遍歷map的過程:
For_range_statement::lower_range_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表實現,插入資料位置是隨機的,所以遍歷過程中新插入的資料不能保證遍歷到。
3、range for channel
遍歷channel是最特殊的,這是由channel的實現機制決定的:
For_range_statement::lower_range_channel
// The loop we generate: // for { // index_temp, ok_temp = <-range // if !ok_temp { // break // } // index = index_temp // original body // }
一直迴圈讀資料,如果有資料則取出,如果沒有則阻塞,如果channel被關閉則退出迴圈
注:
上述註釋中index_temp實際上描述是有誤的,應該為value_temp,因為index對於channel是沒有意義的。
使用index,value接收range返回值會產生一次資料拷貝,視情況考慮不接收,以提高效能
for-range的實現實際上是C風格的for迴圈
到此這篇關於go語言中for range使用方法及避坑指南的文章就介紹到這了,更多相關go語言for range使用內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
【《Go專家程式設計》Go range實現原理及效能優化剖析 https://my.oschina.net/renhc/blog/2396058
【面試官:用過go中的for-range嗎?這幾個問題你能解釋一下原因嗎?】https://zhuanlan.zhihu.com/p/217987219
【Go語言高效能程式設計】https://geektutu.com/post/hpg-range.html
【gofrontend】https://github.com/golang/gofrontend/blob/master/go/statements.cc
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45