Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go for range

Go语言中for和range的性能比较

作者:搬运工李

这篇文章主要为大家详细介绍了Go语言中for和range语句的使用以及性能比较,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下

能GET到的知识点

什么场景使用for和range

1. 从一个遍历开始

1.1万能的range遍历

1.遍历array/slice/strings

array

package main  
import "fmt"  
func main() {  
    var UserIDList = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}  
    for i, v := range UserIDList {  
        fmt.Println(i, v)  
    }  
}

输出:

0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10

slice

package main  
import "fmt"  
func main() {  
    var UserIDList = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}  
    var UerSlice = UserIDList[:]  
    for i, v := range UerSlice {  
        fmt.Println(i, v)  
    }  
}

输出:

0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10

字符串

func main(){
    var Username = "斑斑砖abc"  
    for i, v := range Username {  
        fmt.Println(i, v)  
    }
}

输出:

0 26001
3 26001
6 30742
9 97
10 98
11 99

range进行对array、slice类型遍历一切都正常,但是到了对字符串进行遍历时这里就出问题了,出问题主要在索引这一块。可以看出索引是每个字节的位置,在go语言中的字符串是UTF-8编码的字节序列。而不是单个的Unicode字符。遇到中文字符时需要使用多个字节表示,英文字符一个字节进行表示,索引0-3表示了一个字符及以此完后。

2.遍历map

func ByMap() {  
    m := map[string]int{  
    "one": 1,  
    "two": 2,  
    "three": 3,  
    }  
    for k, v := range m {  
        delete(m, "two")  
        m["four"] = 4  
        fmt.Printf("%v: %v\n", k, v)  
    }  
}

输出:

one: 1
four: 4
three: 3

3.遍历channel

func ByChannel() {  
    ch := make(chan string)  
    go func() {  
        ch <- "a"  
        ch <- "b"  
        ch <- "c"  
        ch <- "d"  
        close(ch)  
    }()  
    time.Sleep(time.Second)
    for n := range ch {  
        fmt.Println(n)  
    }  
}

2.for和range之间奇怪的问题

2.1 无限遍历现象

for

c := []int{1, 2, 3}  
for i := 0; i < len(c); i++ {  
    c = append(c, i)  
    fmt.Println(i)  
}

输出:

1
2
3
.
.
.
15096
15097
15098
15099
15100
15101
15102
15103
15104

range

c := []int{1, 2, 3}  
for _, v := range c {  
    c = append(c, v)  
    fmt.Println(v)  
}

输出:

1
2
3

可以看出for循环一直在永无止境的进行追加元素。 range循环正常。原因:for循环的i < len(c)-1都会进行重新计算一次,造成了永远都不成立。range循环遍历在开始前只会计算一次,如果在循环进行修改也不会影响正常变量。

2.2 在for和range进行修改操作

for

type UserInfo struct {  
    Name string  
    Age int  
}
var UserInfoList = [3]UserInfo{  
    {Name: "John", Age: 25},  
    {Name: "Jane", Age: 30},  
    {Name: "Mike", Age: 28},  
    }  
for i := 0; i < len(UserInfoList); i++ {  
    UserInfoList[i].Age += i  
}  
fmt.Println(UserInfoList)

输出:

0
1
2
[{John 25} {Jane 31} {Mike 30}]

range

var UserInfoList = [3]UserInfo{  
    {Name: "John", Age: 25},  
    {Name: "Jane", Age: 30},  
    {Name: "Mike", Age: 28},  
    }  
for i, info := range UserInfoList {  
    info.Age += i  
}  
fmt.Println(UserInfoList)

输出:

[{John 25} {Jane 30} {Mike 28}]

可以看出for循环进行修改了成功,但是在range循环修改失效,为什么呢?因为range循环返回的是对该值的拷贝,所以修改失效。for循环修相当于进行原地修改了。但如果在for循环里面进行赋值修改操作,那么修改也会进行失效 具体如下

var UserInfoList = [3]UserInfo{  
    {Name: "John", Age: 25},  
    {Name: "Jane", Age: 30},  
    {Name: "Mike", Age: 28},  
}  
for i := 0; i < len(UserInfoList); i++ {  
    fmt.Println(i)  
    item := UserInfoList[i]  
    item.Age += i  
}  
fmt.Println(UserInfoList)

输出:

> [{John 25} {Jane 30} {Mike 28}]

3. Benchmark大比拼

主要是针对大类型结构体

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

for_test.go

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

goos: windows
goarch: amd64
pkg: article/02fortest
cpu: AMD Ryzen 5 5600G with Radeon Graphics
BenchmarkForStruct-12            2503378               474.8 ns/op             0 B/op          0 allocs/op
BenchmarkRangeStruct-12             4983            232744 ns/op               0 B/op          0 allocs/op
PASS
ok      article/02fortest       3.268s

可以看出 for 的性能大约是 range600 倍。

为什么会产生这么大呢?

上述也说过,range遍历会对迭代的值创建一个拷贝。在占据占用较大的结构时每次都需要进行做一次拷贝,取申请大约4kb的内存,显然是大可不必的。所以在对于占据较大的结构时,应该使用for进行变量操作。

总结

如何选择合适的遍历,在针对与测试场景的情况下,图便捷可以使用range,毕竟for循环需要写一堆的条件,初始值等。但是如果遍历的元素是个占用大个内存的结构的话,避免使用range进行遍历。且如果需要进行修改操作的话只能用for遍历来修改,其实range也可以进行索引遍历的,在本文为写,读者可以去尝试一下。

到此这篇关于Go语言中for和range的性能比较的文章就介绍到这了,更多相关Go for range内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文