Golang 迭代器总结

396 阅读3分钟

1. 概述

Go 语言从 1.23 版本开始引入了对迭代器的标准化支持。迭代器是一种用于遍历数据集合的机制,允许开发者以统一的方式处理数组、切片、映射、通道等数据结构。Go 的迭代器设计基于 推迭代器 模式,通过 yield 函数将值推送给调用方。


2. 迭代器的定义

在 Go 1.23 中,迭代器是指符合以下三种函数签名之一的函数:

  • func(yield func() bool)
  • func(yield func(V) bool)
  • func(yield func(K, V) bool)

这些函数被称为 推迭代器,通过调用 yield 函数逐步推出一系列值。yield 函数返回一个布尔值,决定是否继续执行推出操作。如果 yield 返回 false,迭代将提前终止。


3. for-range 对迭代器的支持

Go 1.23 扩展了 for-range 循环的功能,使其支持对函数类型的迭代。开发者可以直接使用 for-range 来遍历自定义迭代器返回的值。

示例:使用 for-range 遍历迭代器

package main

import "fmt"

func Backward[E any](s []E) func(yield func(int, E) bool) {
    return func(yield func(int, E) bool) {
        for i := len(s) - 1; i >= 0; i-- {
            if !yield(i, s[i]) {
                return
            }
        }
    }
}

func main() {
    s := []string{"程序员", "陈明勇"}
    for i, v := range Backward(s) {
        fmt.Println(i, v)
    }
}

运行结果:

1 陈明勇
0 程序员

4. iter

Go 1.23 新增了 iter 包,定义了两种迭代器类型:

  • Seq[V any]:用于单值迭代。
  • Seq2[K, V any]:用于键值对迭代。

示例:使用 iter.Seq

package main

import (
    "fmt"
    "iter"
)

type Set[E comparable] struct {
    m map[E]struct{}
}

func NewSet[E comparable]() Set[E] {
    return Set[E]{m: make(map[E]struct{})}
}

func (s Set[E]) All() iter.Seq[E] {
    return func(yield func(E) bool) {
        for v := range s.m {
            if !yield(v) {
                return
            }
        }
    }
}

func main() {
    set := NewSet[string]()
    set.Add("程序员")
    set.Add("陈明勇")
    for v := range set.All() {
        fmt.Println(v)
    }
}

运行结果:

程序员
陈明勇

5. 推迭代器与拉迭代器

  • 推迭代器:由迭代器主动将值推送给 yield 函数,for-range 直接支持这种模式。
  • 拉迭代器:由调用方主动请求数据,每次调用返回一个值。Go 1.23 提供了 iter.Pull 函数,将推迭代器转换为拉迭代器。

示例:使用 iter.Pull

package main

import (
    "fmt"
    "iter"
)

func main() {
    set := NewSet[string]()
    set.Add("程序员")
    set.Add("陈明勇")
    next, stop := iter.Pull(set.All())
    defer stop()
    for {
        v, ok := next()
        if !ok {
            break
        }
        fmt.Println(v)
    }
}

6. 标准库的支持

Go 1.23 在 slicesmaps 包中新增了与迭代器相关的函数,例如:

  • slices.All([]E) iter.Seq2[int, E]
  • maps.All(map[K]V) iter.Seq2[K, V]

这些函数使得开发者可以更方便地使用迭代器处理切片和映射。


7. 性能与争议

  • 性能:迭代器通过编译器优化(如内联)减少了额外的函数调用开销,性能表现良好。
  • 争议:部分开发者认为迭代器增加了语言的复杂性,破坏了 Go 的简洁性。但也有观点认为,迭代器统一了设计,提升了代码的可维护性。

8. 注意事项

  • yield 返回值:如果 yield 返回 false,迭代器函数应立即停止执行并返回,以避免资源浪费和逻辑错误。
  • 同步与异步迭代器
    • 同步迭代器会阻塞程序向下执行,直到迭代器函数完成。
    • 异步迭代器不会阻塞程序,但需要确保迭代器函数在后台正确终止。

9. 总结

Go 1.23 的迭代器通过标准化设计和 iter 包的支持,为开发者提供了更灵活、高效的迭代方式。虽然引入迭代器带来了一定的学习成本,但它显著提升了代码的可读性和可维护性,尤其是在处理复杂数据结构时。以下是迭代器的核心优势:

  • 统一设计:通过 SeqSeq2 类型,统一了迭代器的实现方式。
  • 高效性能:编译器优化减少了额外的开销。
  • 灵活性:支持同步和异步迭代,适用于多种场景。