一文带大家搞懂Go语言中的迭代器

 更新时间:2025年02月10日 08:58:11   作者:木卯  
迭代器是使用户可在容器对象上遍访的对象,设计人员使用此接口无需关心容器对象的内存分配的实现细节,本文主要为大家详细介绍一下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语言迭代器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解如何在Go中使用Zap管理日志

    详解如何在Go中使用Zap管理日志

    Zap提供了两种类型的日志记录器—Sugared Logger和Logger,可以更好的管理日志,这篇文章主要为大家介绍了使用Zap管理日志的具体方法,需要的可以了解一下
    2023-07-07
  • Go语言操作数据库及其常规操作的示例代码

    Go语言操作数据库及其常规操作的示例代码

    这篇文章主要介绍了Go语言操作数据库及其常规操作的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 如何使用大学教育邮箱下载golang等软件(推荐)

    如何使用大学教育邮箱下载golang等软件(推荐)

    这篇文章主要介绍了如何使用大学教育邮箱下载goland等软件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Go语言defer语句的三种机制整理

    Go语言defer语句的三种机制整理

    在本篇文章里小编给大家分享的是一篇关于Go语言defer语句的三种机制整理,需要的朋友们学习下吧。
    2020-03-03
  • Go Plugins插件的实现方式

    Go Plugins插件的实现方式

    目前 Plugins 仅在 Linux、FreeBSD 和 macOS 上受支持,且只支持 golang 调用,今天通过本文给大家介绍Go Plugins插件的实现方式,感兴趣的朋友一起看看吧
    2021-08-08
  • 浅析Go语言中数组的使用

    浅析Go语言中数组的使用

    数组用于在单个变量中存储相同类型的多个值,而不是为每个值声明单独的变量,这篇文章主要为大家介绍了Go语言中数组的简单使用,需要 的可以参考下
    2023-08-08
  • 一文搞懂Go语言中文件的读写与创建

    一文搞懂Go语言中文件的读写与创建

    这篇文章主要为大家详细介绍了Go语言中文件是如何实现读写与创建的,文中的示例代码讲解详细,对我们学习Go语言有一定帮助,需要的可以参考一下
    2022-07-07
  • Go语言sync.Pool对象池使用场景基本示例

    Go语言sync.Pool对象池使用场景基本示例

    这篇文章主要为大家介绍了Go语言sync.Pool对象池使用场景的基本示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Go 请求兔子识别接口实现流程示例详解

    Go 请求兔子识别接口实现流程示例详解

    这篇文章主要为大家介绍了Go 请求兔子识别接口实现流程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • 深入了解Golang中Slice切片的使用

    深入了解Golang中Slice切片的使用

    本文主要为大家详细介绍了Golang中Slice切片的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2023-02-02

最新评论