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