一文带大家搞懂Go语言中的迭代器
作者:木卯
1. 迭代器简介
1.1 定义
维基百科上迭代器的定义:迭代器(英语:iterator),是使用户可在容器对象(container,例如链表或数组)上遍访的对象,设计人员使用此接口无需关心容器对象的内存分配的实现细节。
1.2 为何需要迭代器
理论上,迭代器的功能可以通过其他方法实现,那为何还需要迭代器呢?迭代器之所以存在,自然是有它的优点的:
- 节省内存: 迭代器不会一次性将所有数据都加载到内存中,而是按需生成数据。比如在逐行读取大文件时使用迭代器能显著减少内存的使用。
- 性能优化:迭代器仅在需要时生成数据,可以减少不必要的计算。比如,数据库查询结果集逐行返回,而不是一次性返回所有记录。
- 解耦数据生成逻辑与遍历逻辑:迭代器负责生成数据,其逻辑是独立的,不受遍历数据逻辑的影响。同样的 ,遍历迭代器时无需考虑数据生成的细节。
2. Go 1.23版本之前的迭代器
在 1.23 版本之前,Go 官方是没有提供传统意义上的迭代器的,如果要用到迭代器的功能,就需要自己实现。下面来说说两种比较有代表性的实现。
面向对象语言的迭代器
在面向对象语言中,Java 中的 Iterator
迭代器是比较有代表性的,下面使用 Go 来实现一个类似的迭代器。
package main import "fmt" type Iterator struct { data []int index int } func NewIterator(data []int) *Iterator { return &Iterator{data: data, index: 0} } func (it *Iterator) HasNext() bool { return it.index < len(it.data) } func (it *Iterator) Next() int { if it.index >= len(it.data) { panic("没有元素了") } item := it.data[it.index] it.index++ return item } func main() { it := NewIterator([]int{1, 2, 3, 4, 5}) for it.HasNext() { fmt.Println(it.Next()) } }
这种面向对象实现的迭代器不够简洁,一般而言,在实际编程中很少自定义这样的迭代器。
函数形式的迭代器
Go 中使用函数形式实现的迭代器,比较有代表性的是使用协程和通道来实现的:
package main import "fmt" func generator(num int) <-chan int { ch := make(chan int) go func() { for i := 0; i < num; i++ { ch <- i } close(ch) }() return ch } func main() { for n := range generator(5) { fmt.Println(n) } }
这种形式的迭代器,相对面向对象的迭代器要简洁得多。不过该方式使用了协程来实现,其性能要差很多。
3. Go 1.23版本的迭代器
Go 1.23 版本提供了一个 iter 包,算是 Go 官方提供了一个标准的迭代器实现。
Go 1.23 的迭代器是一个函数类型,把另一个函数( yield
函数)作为参数,定义迭代器时将元素传递给 yield
函数。迭代器函数会在以下两种情况停止。
- 在序列迭代结束后停止,表示此次迭代结束。
- 在
yield
函数返回false
时停止,表示提前停止迭代。
下面使用 iter 包来实现函数形式的迭代器,iter.Seq[V any]
是 func(yield func(V) bool)
的简称:
package main import ( "fmt" "iter") func generator(num int) iter.Seq[int] { return func(yield func(int) bool) { for i := 0; i < num; i++ { if !yield(i) { return } } } } func main() { for n := range generator(5) { fmt.Println(n) } }
这种形式的迭代器,兼顾了性能和简洁性。
在性能方面,笔者测试了一下,使用 yield
函数 实现的迭代器,比使用协程和通道实现的迭代器,提升了 200 多倍的性能!
简洁性是相对面向对象来说的。有些遗憾的是,相比其他的一些语言,上面的实现在简洁明了方面做得不够好。generator
需要返回一个函数,该函数的参数还是一个函数,还需要判断是否提前停止迭代。这样很不优雅,本来这些是需要隐藏到幕后,由语言本身实现而不是由用户去实现的。
其他语言中一个比较优雅的迭代器实现方式是 Python 的生成器函数,其方式如下:
def generator(num: int): for i in range(num): yield i
上面的代码仅用3行就实现了同样的功能,而且该函数里面无需返回函数,无需判断是否需要提前停止迭代。用户只需关注如何生成数据,然后通过一个 yield
语句就可将数据传给遍历者。
4. 总结
Go 1.23版本之前,Go 官方没有提供迭代器,需要用户自己定义,很不方便。Go 1.23 开始,终于提供了官方的迭代器,弥补了 Go 语言在迭代器方面的缺失,方便了有相关需求的用户,这是可喜的。
Go 语言追求简单,少即是多的理念,这本无可厚非,不过之前的 Go 追求得有些极端了。近年来,Go 终于没那么极端了,为 Go 语言本身和 Go 标准库 添加了许多特性和功能。望 Go 早日成为主流编程语言。
到此这篇关于一文带大家搞懂Go语言中的迭代器的文章就介绍到这了,更多相关Go语言迭代器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!