Golang之Stream流处理

Golang之Stream流处理

有学过Java的朋友应该都知道java8的流Stream,使用一种类似SQL语句从数据库查询数据的直观方式来提供对Java集合进行运算的抽象表达,提高集合类型的处理能力。

List<Integer> transactionsIds = widgets.stream()
				             .filter(b -> b.getColor() == RED)
				             .sorted((x,y) -> x.getWeight() - y.getWeight())
				             .mapToInt(Widget::getWeight)
				             .sum();

Stream流支持链式调用和函数式编程的风格进行数据处理,经过一系列操作加工后,最后由最终操作处理得到最终结果。

实现思路

Stream流的实现原理就是将数据处理流程抽象成了一个数据流,每次加工后返回一个新的流供给使用。

1、链式调用
链式调用的实现,只需每个加工步骤处理完后都返回Stream对象供下一步操作使用

2、流水式处理
想象下工厂的流水线,在环节1将物件加工成结果1,然后将现在的结果1物件传递到环节2加工成结果2。

我们可以理解为数据在Stream流中的加工存储,在Go中可以使用channel作为数据通道,链式调用执行多个操作时使用协程达到异步非阻塞。

Stream流的工作流程跟工厂的生产流程很相似:
1、创建阶段 => 获取数据 (获取原料)
2、加工阶段 => 数据处理 (工厂流水线加工)
3、汇总阶段 => 最终处理 (最终成品)

下面是使用Go语言实现Stream流的三个阶段

一、创建阶段

创建阶段,也即构建Stream流对象

// 方式一:通过参数创建
func BuildByParams(items ...interface{}) Stream

// 方式二:通过函数创建
func BuildByFunc(fn CreateFunc) Stream

二、加工阶段

加工的实现:每次创建一个新channel,从旧channel中读取数据进行处理,而后写入新channel,最后返回一个新channel

加工阶段常见的数据操作:排序、去重、过滤、分组、反转、映射结果等。

// 排序
func (s Stream) Sort(fn SortFunc) Stream

// 去重
func (s Stream) Distinct() Stream

// 过滤
func (s Stream) Filter(fn FilterFunc) Stream

// 分组
func (s Stream) Group(fn GroupFunc) Stream

// 反转
func (s Stream) Reverse() Stream

// 取前n个元素
func (s Stream) Head(n int64) Stream

// 映射元素到对应的处理结果
func (s Stream) Map(fn MapFunc) Stream

...

三、汇总阶段

汇总阶段就是处理成我们想要的结果输出,比如:统计数量、取出某个元素、遍历数据等。

// 统计数量 (此处只列举一个,可自行扩展其他,如:sum...)
func (s Stream) Count() int

// 取出第一个元素
func (s Stream) First() interface{}

// 取出最后元素
func (s Stream) Last() interface{}

// 遍历数据
func (s Stream) ForEach(fn ForEachFunc)

// 根据底层channel进行数据处理
func (s Stream) Handle(fn HandleFunc) (interface{}, error)

...

代码实现

具体代码如下(还可以继续根据需要进行添加):

stream.go


package stream

import (
	"sort"
	"sync"
)

const worker = 10

type (
	// CreateFunc 创建Stream对象方法
	CreateFunc func(source chan<- interface{})

	// FilterFunc 过滤方法
	FilterFunc func(item interface{}) bool

	// GroupFunc 分组方法
	GroupFunc func(item interface{}) interface{}

	// SortFunc 排序方法
	SortFunc func(a, b interface{}) bool

	// MapFunc map映射方法
	MapFunc func(item interface{}) interface{}

	// WalkFunc 异步遍历channel中数据进行处理
	WalkFunc func(item interface{}, pipe chan<- interface{})

	// HandleFunc 处理底层channel方法
	HandleFunc func(pipe <-chan interface{}) (interface{}, error)

	// ForEachFunc 遍历处理方法
	ForEachFunc func(item interface{})

	Stream struct {
		source <-chan interface{}
	}
)

/*#################### 创建阶段 ####################*/

// BuildByParams 方式一:通过参数创建Stream对象
func BuildByParams(items ...interface{}) Stream {
	source := make(chan interface{}, len(items))
	for _, item := range items {
		source <- item
	}
	close(source)

	return ConvertToStream(source)
}

// BuildByFunc 方式二:通过函数创建Stream对象
func BuildByFunc(fn CreateFunc) Stream {
	source := make(chan interface{})

	go func() {
		defer close(source)
		fn(source)
	}()

	return ConvertToStream(source)
}

// ConvertToStream 转换通道数据为Stream
func ConvertToStream(source <-chan interface{}) Stream {
	return Stream{
		source: source,
	}
}

/*#################### 加工阶段 ####################*/

// Sort 排序
func (s Stream) Sort(fn SortFunc) Stream {
	var items []interface{}
	for item := range s.source {
		items = append(items, item)
	}
	sort.Slice(items, func(i, j int) bool {
		return fn(items[i], items[j])
	})

	return BuildByParams(items...)
}

// Distinct 去重
func (s Stream) Distinct(fn GroupFunc) Stream {
	source := make(chan interface{})

	go func() {
		defer close(source)

		keys := make(map[interface{}]struct{})
		for item := range s.source {
			key := fn(item)
			if _, ok := keys[key]; !ok {
				source <- item
				keys[key] = struct{}{}
			}
		}
	}()

	return ConvertToStream(source)
}

// Filter 过滤
func (s Stream) Filter(fn FilterFunc) Stream {
	return s.AsyncExec(func(item interface{}, pipe chan<- interface{}) {
		if fn(item) {
			pipe <- item
		}
	})
}

// Group 根据元素的键分组
func (s Stream) Group(fn GroupFunc) Stream {
	groups := make(map[interface{}][]interface{})
	for item := range s.source {
		key := fn(item)
		groups[key] = append(groups[key], item)
	}

	source := make(chan interface{})
	go func() {
		for _, group := range groups {
			source <- group
		}
		close(source)
	}()

	return ConvertToStream(source)
}

// Reverse 反转
func (s Stream) Reverse() Stream {
	var items []interface{}
	for item := range s.source {
		items = append(items, item)
	}

	for i := len(items)/2 - 1; i >= 0; i-- {
		opp := len(items) - 1 - i
		items[i], items[opp] = items[opp], items[i]
	}

	return BuildByParams(items...)
}

// Head 返回前N个元素
func (s Stream) Head(n int64) Stream {
	if n < 1 {
		panic("n must be greater than 0")
	}

	source := make(chan interface{})

	go func() {
		for item := range s.source {
			n--
			// n可能大于s.source长度
			if n >= 0 {
				source <- item
			}
			if n == 0 {
				close(source)
				// 清空旧channel,以防协程泄漏
				clear(s.source)
			}
		}
		if n > 0 {
			close(source)
		}
	}()

	return ConvertToStream(source)
}

// Map 映射元素到对应的处理结果
func (s Stream) Map(fn MapFunc) Stream {
	return s.AsyncExec(func(item interface{}, pipe chan<- interface{}) {
		pipe <- fn(item)
	})
}

// AsyncExec 协程机制异步执行,数据顺序是随机的
func (s Stream) AsyncExec(fn WalkFunc) Stream {
	// TODO::可优化为配置输入worker数
	// channel中超过10将会被阻塞
	pipe := make(chan interface{}, worker)

	go func() {
		var wg sync.WaitGroup

		// TODO::可增加个channel来控制协程数量,每次执行完读取一次channel释放一个协程位
		for item := range s.source {
			// 在另一个协程中使用
			val := item
			wg.Add(1)
			go func() {
				defer wg.Done()
				fn(val, pipe)
			}()
		}

		wg.Wait()
		close(pipe)
	}()

	return ConvertToStream(pipe)
}

/*#################### 汇总阶段 ####################*/

// Count 统计数量
func (s Stream) Count() (count int) {
	for range s.source {
		count++
	}
	return
}

// First 取出第一个元素
func (s Stream) First() interface{} {
	for item := range s.source {
		// 快速返回第一个元素,清空旧通道
		go clear(s.source)
		return item
	}

	return nil
}

// Last 返回最后的元素
func (s Stream) Last() (item interface{}) {
	for item = range s.source {
	}
	return
}

// ForEach 遍历
func (s Stream) ForEach(fn ForEachFunc) {
	for item := range s.source {
		fn(item)
	}
}

// Handle 自定义处理底层channel
func (s Stream) Handle(fn HandleFunc) (interface{}, error) {
	return fn(s.source)
}

// clear 清空channel
func clear(channel <-chan interface{}) {
	for range channel {
	}
}

简单的ut如下:

stream_test.go


package test

import (
	"demo/stream"
	"fmt"
	"github.com/stretchr/testify/assert"
	"testing"
)



func TestStream_Sort(t *testing.T) {
	var prev int
	// 正序排列
	stream.BuildByParams(1, 5, 3, 4, 2, 6).
		Sort(func(i, j interface{}) bool {
			return i.(int) < j.(int)
		}).
		ForEach(func(item interface{}) {
			fmt.Println(item)
			next := item.(int)
			assert.True(t, prev < next)
			prev = next
		})
}

func TestStream_Distinct(t *testing.T) {
	var result int
	stream.BuildByParams(1, 2, 3, 4, 5, 1, 3).
		Distinct().
		ForEach(func(item interface{}) {
			fmt.Println(item)
			result += item.(int)
		})
	assert.Equal(t, 15, result)
}

func TestStream_Filter(t *testing.T) {
	var result int
	stream.BuildByParams(1, 2, 3, 4, 5).
		Filter(func(item interface{}) bool {
			return item.(int)%2 == 0
		}).
		ForEach(func(item interface{}) {
			fmt.Println(item)
			result += item.(int)
		})
	assert.Equal(t, 6, result)
}

func TestStream_AsyncExec(t *testing.T) {
	var result int
	// 异步执行,数据顺序为随机的
	stream.BuildByParams(1, 2, 3, 4).
		AsyncExec(func(item interface{}, pipe chan<- interface{}) {
			pipe <- item.(int) * item.(int)
		}).
		ForEach(func(item interface{}) {
			fmt.Println(item)
			result += item.(int)
		})
	assert.Equal(t, 30, result)
}

func TestStream_Group(t *testing.T) {
	var groups [][]int
	// 根据元素除以10之后的结果进行分组
	stream.BuildByParams(10, 11, 20, 21, 30, 31).
		Group(func(item interface{}) interface{} {
			return item.(int) / 10
		}).
		ForEach(func(item interface{}) {
			fmt.Println(item)

			v := item.([]interface{})
			var group []int
			for _, each := range v {
				group = append(group, each.(int))
			}
			groups = append(groups, group)
		})

	assert.Equal(t, 3, len(groups))
	for _, group := range groups {
		assert.Equal(t, 2, len(group))
		assert.True(t, group[0]/10 == group[1]/10)
	}
}

func TestStream_Head(t *testing.T) {
	res, _ := stream.BuildByParams(1, 2, 3, 4, 5).
		Head(3).
		Handle(func(pipe <-chan interface{}) (interface{}, error) {
			var result int
			for item := range pipe {
				result += item.(int)
			}
			return result, nil
		})
	fmt.Println(res)
	assert.Equal(t, 6, res)
}

func TestStream_Map(t *testing.T) {
	result, _ := stream.BuildByParams(1, 2, 3, 4, 5).
		Map(func(item interface{}) interface{} {
			return item.(int) * 10
		}).
		Handle(func(pipe <-chan interface{}) (interface{}, error) {
			var result int
			for item := range pipe {
				result += item.(int)
			}
			return result, nil
		})
	assert.Equal(t, 150, result)
}

func TestStream_Reverse(t *testing.T) {
	result := make([]int, 0)
	stream.BuildByParams(1, 2, 3, 4, 5).
		Reverse().
		ForEach(func(item interface{}) {
			fmt.Println(item)

			result = append(result, item.(int))
		})
	fmt.Println(result)
	assert.ElementsMatch(t, []int{5, 4, 3, 2, 1}, result)
}

func TestStream_Count(t *testing.T) {
	assert.Equal(t, 5, stream.BuildByParams(1, 2, 3, 4, 5).Count())

	member := []interface{}{1, 2, 3}
	assert.Equal(t, len(member), stream.BuildByParams(member...).Count())
}

func TestStream_First(t *testing.T) {
	assert.Nil(t, stream.BuildByParams().First())
	assert.Equal(t, "aa", stream.BuildByParams("aa").First())
	assert.Equal(t, "aa", stream.BuildByParams("aa", "bb", "cc").First())
}

总结

Stream流处理就是利用channel当通道,数据当做水流,不断的写入数据到channel中或用协程接收处理达到异步非阻塞的效果
在没有实现Stream流操作之前,一直觉得实现一个Stream流很复杂,不敢想象几百行go的代码就能实现(当然还需要进一步优化和按需迭代),一定要多思考,多动手实践!

我是六涛sheliutao,文章编写总结不易,转载注明出处,喜欢本篇文章的小伙伴欢迎点赞、关注,有问题可以评论区留言或者私信我,相互交流!!!

参考
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sheliutao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值