Go设计模式:Iterator

最常见的迭代模式莫过于循环:

package main

func main() {
	for i := 0; i < 10; i++ {
		println(i)
	}
}

迭代器会依次把元素呈现给我们,当没有元素时,迭代终止。

那么Go语言里还有哪些常见的迭代器呢?比如 sync.Map 里,就有:

package main

import (
	"fmt"
	"sync"
)

func main() {
	aMap := sync.Map{}

	aMap.Store("hello", "world")
	aMap.Store("world", "hello")

	aMap.Range(func(k, v interface{}) bool {
		fmt.Printf("key: %s, value %s\n", k, v)
		return true
	})
}

我们来看看这个 Range 是怎么实现的:

func (m *Map) Range(f func(key, value interface{}) bool) {
	// We need to be able to iterate over all of the keys that were already
	// present at the start of the call to Range.
	// If read.amended is false, then read.m satisfies that property without
	// requiring us to hold m.mu for a long time.
	read, _ := m.read.Load().(readOnly)
	if read.amended {
		// m.dirty contains keys not in read.m. Fortunately, Range is already O(N)
		// (assuming the caller does not break out early), so a call to Range
		// amortizes an entire copy of the map: we can promote the dirty copy
		// immediately!
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		if read.amended {
			read = readOnly{m: m.dirty}
			m.read.Store(read)
			m.dirty = nil
			m.misses = 0
		}
		m.mu.Unlock()
	}

	for k, e := range read.m {
		v, ok := e.load()
		if !ok {
			continue
		}
		if !f(k, v) {
			break
		}
	}
}

// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
	m       map[interface{}]*entry
	amended bool // true if the dirty map contains some key not in m.
}

其中 read.m 是一个map,所以还是使用Go内置的map来做的,那如果我们想自己做一个该咋办呢?我们来参考 golang-set 的实现方式:

先看用法:

package main

import (
	"fmt"

	mapset "github.com/deckarep/golang-set"
)

func main() {
	aSet := mapset.NewSet()
	aSet.Add("hello")
	aSet.Add("world")
	aSet.Add("hello")
	aSet.Add("world")

	for v := range aSet.Iterator().C {
		fmt.Printf("v: %s\n", v.(string))
	}
}

来看看实现:

type Iterator struct {
	C    <-chan interface{}
	stop chan struct{}
}

func (set *threadSafeSet) Iterator() *Iterator {
	iterator, ch, stopCh := newIterator()

	go func() {
		set.RLock()
	L:
		for elem := range set.s {
			select {
			case <-stopCh:
				break L
			case ch <- elem:
			}
		}
		close(ch)
		set.RUnlock()
	}()

	return iterator
}

可以看出来,其实它也是通过底层的for循环,加channel来实现的,我们自己来实现一个:

package main

import (
	"fmt"
)

// Iterator 声明接口
type Iterator interface {
	Iterator(m iterSet) Iter
}

// Iter 迭代器的实现
type Iter struct {
	C chan interface{}
}

func newIter(i *iterSet) Iter {
	iter := Iter{make(chan interface{})}

	go func() {
		for k := range i.m {
			iter.C <- k
		}
		close(iter.C)
	}()

	return iter
}

// 我们自己的set
type iterSet struct {
	m map[string]bool
}

// Add 添加元素
func (i *iterSet) Add(k string) {
	i.m[k] = true
}

// Iterator 返回一个迭代器
func (i *iterSet) Iterator() Iter {
	return newIter(i)
}

func main() {
	aSet := iterSet{map[string]bool{}}
	aSet.Add("hello")
	aSet.Add("hello")
	aSet.Add("world")
	aSet.Add("world")

	iter := aSet.Iterator()

	for v := range iter.C {
		fmt.Printf("key: %s\n", v.(string))
	}
}

duang,搞定!


参考资料:


更多文章
  • 使用Go语言实现一个异步任务框架
  • Go使用gdb调试
  • Golang flag源码阅读及自己实现
  • Golang ASM简明教程
  • Golang context源码阅读与分析
  • Golang中的并发控制
  • 善用闭包(closure)让Go代码更优雅
  • Golang的可选参数实践
  • FreeBSD ipfw使用教程
  • Golang expvar库源码阅读
  • Golang SQL生成库 Squirrel 教程及源码阅读
  • Golang validator使用教程
  • 使用Redis的Stream模块实现群聊功能
  • 价值编程与职业发展
  • 解决k8s cron无法读取环境变量的问题