共计 4118 个字符,预计需要花费 11 分钟才能阅读完成。
本篇文章基于 Golang 1.17.2
话说胖虎上次没有问到实习生,感觉实习生底子不错,最近闲来无事,决定在考考实习生。
for 循环的奇怪景象
胖虎:以下代码输入什么
package main
import "fmt"
func main() {s := []int{0, 1}
for num := 0; num < len(s); num++ {s = append(s, s[num])
}
fmt.Printf("s 的值是:%+v\n", s)
}
实习生吐口而出:
[0 1 0 1]
胖虎笑着说:不要焦急答复,思考三秒后在说出你的答案。
实习生: 难道不对?
胖虎看着实习生纳闷的表情,说:咱们来执行下吧
居然是死循环!!!实习生差点喊出来,同时发现胖虎奋斗多年的笔记本,风扇吭吭唧唧不愿意的开始干活了。
胖虎:如果想要 0 1 0 1,应该怎么改呢?
实习生:难道用 range?边说边敲下以下代码。
package main
import "fmt"
func main() {s := []int{0, 1}
for _, value := range s {s = append(s, value)
}
fmt.Printf("s 的值是:%+v\n", s)
}
胖虎:那你晓得为什么吗?
实习生:范畴循环在迭代过程中,难道是迭代值的拷贝?我再试下吧。
package main
import "fmt"
func main() {s := []int{0, 1}
for _, value := range s {value += 10}
fmt.Printf("s 的值是:%+v\n", s)
}
实习生:跟我猜测的一样。
胖虎点点头:范畴遍历在开始遍历数据之前,会先拷贝一份被遍历的数据,所以在遍历过程中去批改被遍历的数据,只是批改拷贝的数据,不会影响到原数据。
而一般 for 循环,会始终一直追加数据到切片,对原数据产生影响。
胖虎:顺便温习一下这两个的应用语法吧
for 应用语法
s := "test"
// 常见的 for 循环,反对初始化语句。for i, n := 0, len(s); i < n; i++ {println(string(s[i]))
}
initNum := 0
intSlice := []int{0, 1}
sliceLen := len(intSlice)
for initNum < sliceLen {
initNum++
fmt.Println(intSlice[initNum])
}
for {// 代替 while (true) {}
println(s) // 代替 for (;;) {}}
for 常见应用场景
由此可见 for 常见的应用场景是字符串,数组,切片和有限循环。但须要留神的是 for 循环字符串的时候,后果想要为字符串的时候,须要 string 转换一下,而有的编程语言不须要,比如说世界上最好的语言。
当然应用 range 遍历字符串就没有这个问题。
range 应用语法
words := []string{"Go", "Java", "C++"}
for i, value := range words {words = append(words, "test")
fmt.Println(i, value)
}
test := "abc"
// 疏忽 value
for i := range s {fmt.Println(s[i])
}
range 常见应用场景
Go 语言中,range 应用场景除了数组 (array)、切片(slice),还能够很不便字典(map) 和信道(chan)
遍历 map
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
for k, v := range m {delete(m, "two")
m["four"] = 4
fmt.Printf("key:%v, value:%v\n", k, v)
}
输入后果:
key:two, value:2
key:three, value:3
key:one, value:1
须要留神的是:
- 和切片不同的是,迭代过程中,删除还未迭代到的键值对,则该键值对不会被迭代。
- 在迭代过程中,如果创立新的键值对,那么新增键值对,可能被迭代,也可能不会被迭代。
- 针对 nil 字典,迭代次数为 0
遍历 channel
ch := make(chan string)
go func() {
ch <- "听我说"
ch <- "谢谢你"
ch <- "因为有你"
ch <- "和煦了四季"
close(ch)
}()
for n := range ch {fmt.Println(n)
}
后果如下:
听我说
谢谢你
因为有你
和煦了四季
range 循环性能肯定比 for 差吗?
[]int
package main
import (
"math/rand"
"testing"
"time"
)
func generateWithCap(n int) []int {
// 生成不同系列的随机数
rand.Seed(time.Now().UnixNano())
nums := make([]int, 0, n)
for i := 0; i < n; i++ {nums = append(nums, rand.Int())
}
return nums
}
func BenchmarkForIntSlice(b *testing.B) {nums := generateWithCap(1024 * 1024)
for i := 0; i < b.N; i++ {len := len(nums)
var tmp int
for k := 0; k < len; k++ {tmp = nums[k]
}
_ = tmp
}
}
func BenchmarkRangeIntSlice(b *testing.B) {nums := generateWithCap(1024 * 1024)
for i := 0; i < b.N; i++ {
var tmp int
for _, num := range nums {tmp = num}
_ = tmp
}
}
执行后果如下:
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
BenchmarkForIntSlice-8 3552 334038 ns/op
BenchmarkRangeIntSlice-8 3544 321965 ns/op
名词解释:
BenchmarkForIntSlice-8,即 GOMAXPROCS,默认等于 CPU 核数。
3552 代表运行了多少次
334038 ns/op 每次执行均匀工夫,
由此可见 , 遍历 []int 类型的切片,for 与 range 性能两者简直没有区别。
[]struct
如果是略微简单一点的 []struct
类型呢?
type Item struct {
id int
val [4096]byte
}
//for 循环
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
}
}
//range 循环只取下标
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
}
}
//range 循环取值
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: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
BenchmarkForStruct-8 3373856 359.7 ns/op
BenchmarkRangeIndexStruct-8 3587757 329.0 ns/op
BenchmarkRangeStruct-8 3478 288161 ns/op
- 仅遍历下标的状况下,for 和 range 的性能简直是一样的。
items
的每一个元素的类型是一个构造体类型Item
,Item
由两个字段形成,一个类型是 int,一个是类型是[4096]byte
,也就是说每个Item
实例须要申请约 4KB 的内存。- 在这个例子中,for 的性能大概是 range (同时遍历下标和值) 的 2000 倍。
[]*struct{}
func generateItems(n int) []*Item {items := make([]*Item, 0, n)
for i := 0; i < n; i++ {items = append(items, &Item{id: i})
}
return items
}
//for 循环指针
func BenchmarkForPointer(b *testing.B) {items := generateItems(1024)
for i := 0; i < b.N; i++ {length := len(items)
var tmp int
for k := 0; k < length; k++ {tmp = items[k].id
}
_ = tmp
}
}
//range 循环指针
func BenchmarkRangePointer(b *testing.B) {items := generateItems(1024)
for i := 0; i < b.N; i++ {
var tmp int
for _, item := range items {tmp = item.id}
_ = tmp
}
}
执行后果:
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
BenchmarkForPointer-8 734905 1545 ns/op
BenchmarkRangePointer-8 709748 1558 ns/op
切片元素从构造体 Item
替换为指针 *Item
后,for 和 range 的性能简直是一样的。而且应用指针还有另一个益处,能够间接批改指针对应的构造体的值。
所以说 for 不肯定比 range 快。
实习生:那我总结一下吧。
胖虎:总结的不错,下次不要总结了。
实习生:那我走?
胖虎:哈哈哈哈,开玩笑的
参考文章:https://geektutu.com/post/hpg…