前言

for i:通过下标迭代。
for range:通过值拷贝迭代。
本文主要说明通过 for i 和 for range 迭代数据结构的性能差异。
注意:以下 1 和 2 两种迭代都是通过下标迭代,3 或 4 才是通过值拷贝迭代。

  1. // 1
  2. for i := 0; i < len(nums); i++ {...}
  3. // 2
  4. for i := range nums
  5. // 3
  6. for _, num := range nums
  7. // 4
  8. for i, num := range nums

差异

一、for i 迭代能够修改数据,而 for range 迭代不能。
举个例子证明:

  1. type Person struct {
  2. Name string
  3. age int
  4. }
  5. func main() {
  6. students := []Person{
  7. {Name: "zwjason", age: 21},
  8. {Name: "bakanrian", age: 22},
  9. }
  10. // for range 尝试修改
  11. for _, student := range students {
  12. student.age = 20
  13. }
  14. for _, student := range students {
  15. fmt.Printf("%s %d\n", student.Name, student.age)
  16. }
  17. // for i 尝试修改
  18. for i := range students {
  19. students[i].age = 20
  20. }
  21. for _, student := range students {
  22. fmt.Printf("%s %d\n", student.Name, student.age)
  23. }
  24. }
  25. 输出如下:
  26. zwjason 21
  27. bakanrian 22 // 修改失败
  28. zwjason 20
  29. bakanrian 20 // 修改成功

可以看到通过 for range 迭代修改失败,因为它是值拷贝迭代,student 只是复制了对应的值。
而 for i 迭代修改成功,因为它是直接对原数组操作。

二、当迭代的值占内存较大时,for range 的性能急剧下降。
比如当时用 for range 迭代如下数据结构:

  1. type Person struct {
  2. Name string
  3. scores [4096]int
  4. }

忽略 Name,一个 Person 实例占用内存至少为 84096 = 32 KB 。
原因是每次迭代都要拷贝至少 32 KB 的数据。
使用指针类型 []
Person 可以避免这个问题,因为复制一个指针的代价一般只有 64 B (在 64 位机器)。