欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

go語言中for?range使用方法及避坑指南

 更新時(shí)間:2022年09月07日 14:25:57   作者:peachesTao  
Go中的for range組合可以和方便的實(shí)現(xiàn)對(duì)一個(gè)數(shù)組或切片進(jìn)行遍歷,但是在某些情況下使用for range時(shí)很可能就會(huì)被"坑",下面這篇文章主要給大家介紹了關(guān)于go語言中for?range使用方法及避坑指南的相關(guān)資料,需要的朋友可以參考下

前言

for range語句是業(yè)務(wù)開發(fā)中編寫頻率很高的代碼,其中會(huì)有一些常見的坑,看完這篇文章會(huì)讓你少入坑。

for range基本用法

range是Golang提供的一種迭代遍歷手段,可操作的類型有數(shù)組、切片、string、map、channel等

1、遍歷數(shù)組

myArray := [3]int{1, 2, 3}
for i, ele := range myArray {
    fmt.Printf("index:%d,element:%d\n", i, ele)
    fmt.Printf("index:%d,element:%d\n", i, myArray[i])
}

直接取元素或通過下標(biāo)取

2、遍歷slice

mySlice := []string{"I", "am", "peachesTao"}
for i, ele := range mySlice {
    fmt.Printf("index:%d,element:%s\n", i, ele)
    fmt.Printf("index:%d,element:%s\n", i, mySlice[i])
}

直接取元素或通過下標(biāo)取

3、遍歷string

s:="peachesTao"
for i,item := range s {
   fmt.Println(string(item))
   fmt.Printf("index:%d,element:%s\n", i, string(s[i]))
}

直接取元素或通過下標(biāo)取

注意:循環(huán)體中string中的元素實(shí)際上是byte類型,需要轉(zhuǎn)換為字面字符

4、遍歷map

myMap := map[int]string{1:"語文",2:"數(shù)學(xué)",3:"英語"}
for key,value := range myMap {
    fmt.Printf("key:%d,value:%s\n", key, value)
    fmt.Printf("key:%d,value:%s\n", key, myMap[key])
}

直接取元素或通過下標(biāo)取

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:%d\n", c)
 }
}()

channel遍歷是循環(huán)從channel中讀取數(shù)據(jù),如果channel中沒有數(shù)據(jù),則會(huì)阻塞等待,如果channel已被關(guān)閉,則會(huì)退出循環(huán)。

for range 和 for的區(qū)別

for range可以直接訪問目標(biāo)對(duì)象中的元素,而for必須通過下標(biāo)訪問

for frange可以訪問map、channel對(duì)象,而for不可以

for range容易踩的坑

下面的例子是將mySlice中每個(gè)元素的后面都加上字符"-new"

mySlice := []string{"I", "am", "peachesTao"}
for _, ele := range mySlice {
    ele=ele+"-new"
}
fmt.Println(mySlice)

結(jié)果:

[I am peachesTao]

打印mySlice發(fā)現(xiàn)元素并沒有更新,為什么會(huì)這樣?

原因是for range語句會(huì)將目標(biāo)對(duì)象中的元素copy一份值的副本,修改副本顯然不能對(duì)原元素產(chǎn)生影響

為了證明上述結(jié)論,在遍歷前和遍歷中打印出元素的內(nèi)存地址

mySlice := []string{"I", "am", "peachesTao"}
fmt.Printf("遍歷前首元素內(nèi)存地址:%p\n",&mySlice[0])
for _, ele := range mySlice {
    ele=ele+"-new"
    fmt.Printf("遍歷中元素內(nèi)存地址:%p\n",&ele)
}
fmt.Println(mySlice)

結(jié)果:

遍歷前第一個(gè)元素內(nèi)存地址:0xc000054180
遍歷前第二個(gè)元素內(nèi)存地址:0xc000054190
遍歷前第三個(gè)元素內(nèi)存地址:0xc0000541a0
遍歷中元素內(nèi)存地址:0xc000010200
遍歷中元素內(nèi)存地址:0xc000010200
遍歷中元素內(nèi)存地址:0xc000010200
[I am peachesTao]

可以得出兩個(gè)結(jié)論:

  • 遍歷體中的元素內(nèi)存地址已經(jīng)發(fā)生了變化,生成了元素副本,至于產(chǎn)生副本的原因在“for range底層原理”段落中會(huì)有介紹
  • 遍歷體中的只生成了一個(gè)全局的元素副本變量,不是每個(gè)元素都會(huì)生成一個(gè)副本,這個(gè)特點(diǎn)也值得大家注意,否則會(huì)踩坑。

比如遍歷mySlice元素生成一個(gè)[]*string類型的mySliceNew,要通過一個(gè)中間變量取中間變量的地址(或者通過下標(biāo)的形式訪問元素也可以)加入mySliceNew,如果直接取元素副本的地址會(huì)導(dǎo)致mySliceNew中所有元素都是一樣的,如下:

mySlice := []string{"I", "am", "peachesTao"}
var mySliceNew []*string
for _, item := range mySlice {
    itemTemp := item
    mySliceNew = append(mySliceNew, &itemTemp)
    //mySliceNew = append(mySliceNew, &item) 錯(cuò)誤的做法
}

回到剛才那個(gè)問題,如何能在遍歷中修改元素呢?答案是直接通過下標(biāo)訪問slice中的元素對(duì)其賦值,如下:

mySlice := []string{"I", "am", "peachesTao"}
for i, _ := range mySlice {
    mySlice[i] = mySlice[i]+"-new"
}
fmt.Println(mySlice)

結(jié)果:

[I-new am-new peachesTao-new]

可以看到元素已經(jīng)被修改

for range和for性能比較

我們定義一個(gè)結(jié)構(gòu)體Item,包含int類型的id字段,對(duì)結(jié)構(gòu)體數(shù)組分別使用for、for range item、for range index的方式進(jìn)行遍歷,下面是測(cè)試代碼(直接引用“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
  }
}

運(yùn)行基準(zhǔn)測(cè)試命令:

go test -bench . test/for_range_performance_test.go

測(cè)試結(jié)果:

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的方式遍歷性能幾乎無差異

下面我們?cè)贗tem結(jié)構(gòu)體添加一個(gè)byte類型長(zhǎng)度為4096的數(shù)組字段val

type Item struct {
  id  int
  val [4096]byte
}

再運(yùn)行一遍基準(zhǔn)測(cè)試,結(jié)果如下:

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通過下標(biāo)遍歷元素的性能跟for相差不大
  • for range直接遍歷元素的性能比for慢近1000倍

結(jié)論:

  • for range通過下標(biāo)遍歷元素的性能跟for相差不大
  • for range直接遍歷元素的性能在元素為小對(duì)象的情況下跟for相差不大,在元素為大對(duì)象的情況下比for慢很多

for range的底層原理

對(duì)于for-range語句的實(shí)現(xiàn),可以從編譯器源碼中找到答案。

編譯器源碼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實(shí)際上是一個(gè)C風(fēng)格的循環(huán)結(jié)構(gòu)。range支持string、數(shù)組、數(shù)組指針、切片、map和channel類型,對(duì)于不同類型有些細(xì)節(jié)上的差異。

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前會(huì)先獲得slice的長(zhǎng)度len_temp作為循環(huán)次數(shù),循環(huán)體中,每次循環(huán)會(huì)先獲取元素值,如果for-range中接收index和value的話,則會(huì)對(duì)index和value進(jìn)行一次賦值,這就解釋了對(duì)大元素進(jìn)行遍歷會(huì)影響性能,因?yàn)榇髮?duì)象賦值會(huì)產(chǎn)生gc

由于循環(huán)開始前循環(huán)次數(shù)就已經(jīng)確定了,所以循環(huán)過程中新添加的元素是沒辦法遍歷到的。

另外,數(shù)組與數(shù)組指針的遍歷過程與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時(shí)沒有指定循環(huán)次數(shù),循環(huán)體與遍歷slice類似。由于map底層實(shí)現(xiàn)與slice不同,map底層使用hash表實(shí)現(xiàn),插入數(shù)據(jù)位置是隨機(jī)的,所以遍歷過程中新插入的數(shù)據(jù)不能保證遍歷到。

3、range for channel

遍歷channel是最特殊的,這是由channel的實(shí)現(xiàn)機(jī)制決定的:

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
//   }

一直循環(huán)讀數(shù)據(jù),如果有數(shù)據(jù)則取出,如果沒有則阻塞,如果channel被關(guān)閉則退出循環(huán)

注:

上述注釋中index_temp實(shí)際上描述是有誤的,應(yīng)該為value_temp,因?yàn)閕ndex對(duì)于channel是沒有意義的。

總結(jié)

使用index,value接收range返回值會(huì)產(chǎn)生一次數(shù)據(jù)拷貝,視情況考慮不接收,以提高性能

for-range的實(shí)現(xiàn)實(shí)際上是C風(fēng)格的for循環(huán)

到此這篇關(guān)于go語言中for range使用方法及避坑指南的文章就介紹到這了,更多相關(guān)go語言for range使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

參考資料

【《Go專家編程》Go range實(shí)現(xiàn)原理及性能優(yōu)化剖析 https://my.oschina.net/renhc/blog/2396058

【面試官:用過go中的for-range嗎?這幾個(gè)問題你能解釋一下原因嗎?】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

相關(guān)文章

  • golang中cache組件的使用及groupcache源碼解析

    golang中cache組件的使用及groupcache源碼解析

    本篇主要解析groupcache源碼中的關(guān)鍵部分, lru的定義以及如何做到同一個(gè)key只加載一次。緩存填充以及加載抑制的實(shí)現(xiàn)方法,本文重點(diǎn)給大家介紹golang中cache組件的使用及groupcache源碼解析,感興趣的朋友一起看看吧
    2021-06-06
  • 簡(jiǎn)單聊聊為什么說Go語言字符串是不可變的

    簡(jiǎn)單聊聊為什么說Go語言字符串是不可變的

    最近有讀者留言說,平時(shí)在寫代碼的過程中,是會(huì)對(duì)字符串進(jìn)行修改的,但網(wǎng)上都說 Go 語言字符串是不可變的,這是為什么呢,本文就來和大家簡(jiǎn)單講講
    2023-05-05
  • Golang創(chuàng)建第一個(gè)web項(xiàng)目(Gin+Gorm)

    Golang創(chuàng)建第一個(gè)web項(xiàng)目(Gin+Gorm)

    本文主要介紹了Golang創(chuàng)建第一個(gè)web項(xiàng)目(Gin+Gorm),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-06-06
  • go語言同步教程之條件變量

    go語言同步教程之條件變量

    這篇文章主要給大家介紹了關(guān)于go語言同步教程之條件變量的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • 詳解go中的引用類型

    詳解go中的引用類型

    這篇文章主要介紹了go中的引用類型,文中給大家提到了值類型和引用類型的區(qū)別,通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Go語言實(shí)現(xiàn)圖片快遞信息識(shí)別的簡(jiǎn)易方法

    Go語言實(shí)現(xiàn)圖片快遞信息識(shí)別的簡(jiǎn)易方法

    這篇文章主要為大家介紹了Go語言實(shí)現(xiàn)圖片快遞信息識(shí)別的簡(jiǎn)易方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Golang實(shí)現(xiàn)簡(jiǎn)易的命令行功能

    Golang實(shí)現(xiàn)簡(jiǎn)易的命令行功能

    這篇文章主要為大家詳細(xì)介紹了如何通過Golang實(shí)現(xiàn)一個(gè)簡(jiǎn)易的命令行功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下
    2023-02-02
  • golang中結(jié)構(gòu)體嵌套接口的實(shí)現(xiàn)

    golang中結(jié)構(gòu)體嵌套接口的實(shí)現(xiàn)

    本文主要介紹了golang中結(jié)構(gòu)體嵌套接口的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類型操作

    golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類型操作

    這篇文章主要介紹了golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類型操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言使用漏桶算法和令牌桶算法來實(shí)現(xiàn)API限流

    Go語言使用漏桶算法和令牌桶算法來實(shí)現(xiàn)API限流

    為防止服務(wù)器被過多的請(qǐng)求壓垮,限流是一個(gè)至關(guān)重要的技術(shù)手段,下面我們就來看看如何使用漏桶算法和令牌桶算法來實(shí)現(xiàn) API 的限流吧
    2024-11-11

最新評(píng)論