本文将带你深入了解 golang 中 关于 for 循环的使用细节,以及如何避免在开发过程中犯一些错误,导致代码执行速度极慢,甚至引发一些未知的错误。别人在看到你的代码时,也不会内心在那吐槽了。。。

案例

在日常开发中经常看到的一些代码样例:

样例一:
for i := 0; i < getCount(); i++ {
    // do something
}

for i := 0; i < len(list); i++ {
    // do something
}
样例二:
for i, v := range list {
    // do something
}

for _, v := range list {
    // do something
}

上面这些示例代码,乍一看去没什么问题,挺工整的是吧,其实有很多坑在里面,下面一一进行分析。

分析

样例一:
fori数组/切片/map/channellen()

下面我写了个简单的测试代码,一眼就可以看出来问题:

func getCount() int {
	var count int
	for i := 0; i < 10; i++ {
		count++
	}
	fmt.Println("getCount:", count)
	return count
}

func main() {
	for i := 0; i < getCount(); i++ {
		fmt.Println("count:", i)
	}
}

输出如下:

getCount: 10
count: 0
getCount: 10
count: 1
getCount: 10
count: 2
getCount: 10
count: 3
getCount: 10
count: 4
getCount: 10
count: 5
getCount: 10
count: 6
getCount: 10
count: 7
getCount: 10
count: 8
getCount: 10
count: 9
getCount: 10

每次循环除了打印当前的 count 值之外,getCount() 方法中的输出也执行了,说明每次循环都重新调用 getCount() 方法获取 count 值。假设在实际开发中,这个getCount()是一个比较耗时的操作,那么你的代码运行速度将会非常慢。

样例二:
数组/切片/map/channelvvv数组/切片/map/channel

下面用一个例子来证明 range 循环是拷贝的值:

func main() {
	persons := []struct {
		no int
	}{
		{no: 1},
		{no: 2},
		{no: 3},
	}
	for _, s := range persons {
		s.no += 10
	}
	fmt.Println(persons)
	personsLen := len(persons)
	for i := 0; i < personsLen; i++ {
		persons[i].no += 100
	}
	fmt.Println(persons)
}

输出结果如下:

[{1} {2} {3}]
[{101} {102} {103}]

persons 是一个长度为 3 的切片,每个元素是一个结构体。
使用 range 迭代时,试图将每个结构体的 no 字段增加 10,但修改无效,因为 range 返回的是拷贝。
使用 fori 迭代时,将每个结构体的 no 字段增加 100,修改有效。

优化

通过上面的分析,知道每个 for 循环的问题后,优化方案就很简单了。

样例一:
count := getCount()
for i := 0; i < count; i++ {
    // do something
}

listLen := len(list)
for i := 0; i < listLen; i++ {
    // do something
}

样例一的优化很简单,就是将循环退出条件的方法提出,用一个临时变量存储,这样每次循环直接读取临时变量即可。

样例二:
for i := range list {
    v := list[i]
    // do something
}

样例二的优化就是不再直接通过 range 获取元素值,而是只获取下标,然后通过下标取值,实际开发中应尽量避免样例二的第二种循环写法,还有一种优化方式就是,将list 中存储的元素设置为结构体指针,这样复制的就只是结构体的地址,性能也不会下降太多。如果list中存储的元素是比较简单的结构,如整形数组/切片这种,也可以使用上面的遍历方式,性能也不会差太多。不过为了避免出现问题,还是不要怕麻烦,尽量采用只返回下标的写法。

以上就是我整理的关于 golang for 循环使用及优化需要注意的地方,有表述不对,或理解错误的欢迎指正。本人也是闲来无聊,分享下开发过程中遇到的一些问题,记录一下而已。