# go-stream **Repository Path**: javaerxue/go-stream ## Basic Information - **Project Name**: go-stream - **Description**: 一个类似于java stream 的用golang 实现的 stream库 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-04-15 - **Last Updated**: 2023-04-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Go-Stream ## 简介   在JAVA中,涉及到对数组、Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行处理。那么在Go中用的多的是切片,那么这里基于Java的stream的操作习惯用Go语言( 1.18+)的泛型和通道实现了一些简单的流操作功能。 > go-stream代码地址:https://github.com/todocoder/go-stream ## Stream 介绍   可以将Stream流操作分为3种类型:Stream的生成,Stream中间处理,Stream的终止 ### Stream的生成   主要负责新建一个Stream流,或者基于现有的数组创建出新的Stream流。 | API | 功能说明 | |------------------|---------------------------------------------------------------| | Of() | 通过可变参数`(values ...T)`创建出一个新的stream串行流对象 | | OfParallel() | 通过可变参数`(values ...T)`创建出一个可并行执行stream串行流对象 | | OfFrom() | 通过方法生成`(generate func(source chan<- T))`创建出一个新的stream串行流对象 | | OfFromParallel() | 通过方法生成`(generate func(source chan<- T))`创建出一个可并行执行stream串行流对象 | | Concat() | 多个流拼接的方式创建出一个串行执行stream串行流对象 | ### Stream中间处理   主要负责对Stream进行处理操作,并返回一个新的Stream对象,中间处理操作可以进行叠加。 | API | 功能说明 | |------------|-------------------------------------------------------------------| | Filter() | 按照条件过滤符合要求的元素, 返回新的stream流 | | Map() | 按照条件将已有元素转换为另一个对象类型,一对一逻辑,返回新类型的stream流 | | FlatMap() | 按照条件将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流 | | Skip() | 跳过当前流前面指定个数的元素 | | Limit() | 仅保留当前流前面指定个数的元素,返回新的stream流 | | Concat() | 多个流拼接到当前流下 | | Distinct() | 按照条件去重符合要求的元素, 返回新的stream流 | | Sorted() | 按照条件对元素进行排序, 返回新的stream流 | | Reverse() | 对流中元素进行返转操作 | | Peek() | 对stream流中的每个元素进行逐个遍历处理,返回处理后的stream流 | ### Stream的终止   通过终止函数操作之后,Stream流将会结束,最后可能会执行某些逻辑处理,或者是按照要求返回某些执行后的结果数据。 | API | 功能说明 | |-------------|---------------------------------------| | FindFirst() | 获取第一个元素 | | FindLast() | 获取最后一个元素 | | ForEach() | 对元素进行逐个遍历,然后执行给定的处理逻辑 | | Reduce() | 对流中元素进行聚合处理 | | AnyMatch() | 返回此流中是否存在元素满足所提供的条件 | | AllMatch() | 返回此流中是否全都满足条件 | | NoneMatch() | 返回此流中是否全都不满足条件 | | Count() | 返回此流中元素的个数 | | Max() | 返回stream处理后的元素最大值 | | Min() | 返回stream处理后的元素最小值 | | ToSlice() | 将流处理后转化为切片 | | Collect() | 将流转换为指定的类型,通过collectors.Collector进行指定 | ## go-stream的使用 ### 库的引入   由于用到了泛型,支持的版本为golang 1.18+ 1. go.mod 中加入如下配置 ``` require github.com/todocoder/go-stream v1.0.0 ``` 2. 执行 ```shell go mod tidy -go=1.20 go mod download ``` ### ForEach、Peek的使用   ForEach和Peek都可以用于对元素进行遍历然后逐个的进行处理。 但Peek属于中间方法,而ForEach属于终止方法。也就是说Peek只能在流中间处理元素,没法直接执行得到结果,其后面必须还要有其它终止操作的时候才会被执行;而ForEach作为无返回值的终止方法,则可以直接执行相关操作。 #### ForEach ```go package todocoder type TestItem struct { itemNum int itemValue string } func TestForEachAndPeek(t *testing.T) { // ForEach stream.Of( TestItem{itemNum: 1, itemValue: "item1"}, TestItem{itemNum: 2, itemValue: "item2"}, TestItem{itemNum: 3, itemValue: "item3"}, ).ForEach(func(item TestItem) { fmt.Println(item.itemValue) }) // Peek stream.Of( TestItem{itemNum: 1, itemValue: "item1"}, TestItem{itemNum: 2, itemValue: "item2"}, TestItem{itemNum: 3, itemValue: "item3"}, ).Peek(func(item *TestItem) { item.itemValue = item.itemValue + "peek" }).ForEach(func(item TestItem) { fmt.Println(item.itemValue) }) } ``` 结果如下: ``` item1 item2 item3 item1peek item2peek item3peek ``` 从代码及结果中得知,ForEach只是用来循环流中的元素。而Peek可以在流中间修改流中的元素。 ### Filter、Sorted、Distinct、Skip、Limit、Reverse   这几个是go-stream中比较常用的中间处理方法,具体说明在上面已标出。使用的话我们可以在流中一个或多个的组合便用。 ```go package todocoder func TestStream(t *testing.T) { // ForEach res := stream.Of( TestItem{itemNum: 7, itemValue: "item7"}, TestItem{itemNum: 6, itemValue: "item6"}, TestItem{itemNum: 1, itemValue: "item1"}, TestItem{itemNum: 2, itemValue: "item2"}, TestItem{itemNum: 3, itemValue: "item3"}, TestItem{itemNum: 4, itemValue: "item4"}, TestItem{itemNum: 5, itemValue: "item5"}, TestItem{itemNum: 5, itemValue: "item5"}, TestItem{itemNum: 5, itemValue: "item5"}, TestItem{itemNum: 8, itemValue: "item8"}, TestItem{itemNum: 9, itemValue: "item9"}, ).Filter(func(item TestItem) bool { // 过滤掉1的值 return item.itemNum != 4 }).Distinct(func(item TestItem) any { // 按itemNum 去重 return item.itemNum }).Sorted(func(a, b TestItem) bool { // 按itemNum升序排序 return a.itemNum < b.itemNum }).Skip(1).Limit(6).Reverse().Collect(collectors.ToSlice[TestItem]()) fmt.Println(res) } ``` 1. 使用Filter过滤掉1的值 2. 通过Distinct对itemNum 去重(在第1步的基础上,下面同理在上一步的基础上) 3. 通过Sorted 按itemNum升序排序 4. 用Skip 从下标为1的元素开始 5. 使用Limit截取排在前6位的元素 6. 使用Reverse 对流中元素进行返转操作 7. 使用collect终止操作将最终处理后的数据收集到Slice中 结果: ``` [{8 item8} {7 item7} {6 item6} {5 item5} {3 item3} {2 item2}] ``` ### AllMatch、AnyMatch、NoneMatch、Count、FindFirst、FindLast   这些方法,均属于这里说的简单结果终止方法。代码如下: ```go package todocoder func TestSimple(t *testing.T) { allMatch := stream.Of( TestItem{itemNum: 7, itemValue: "item7"}, TestItem{itemNum: 6, itemValue: "item6"}, TestItem{itemNum: 8, itemValue: "item8"}, TestItem{itemNum: 1, itemValue: "item1"}, ).AllMatch(func(item TestItem) bool { // 返回此流中是否全都==1 return item.itemNum == 1 }) fmt.Println(allMatch) anyMatch := stream.Of( TestItem{itemNum: 7, itemValue: "item7"}, TestItem{itemNum: 6, itemValue: "item6"}, TestItem{itemNum: 8, itemValue: "item8"}, TestItem{itemNum: 1, itemValue: "item1"}, ).Filter(func(item TestItem) bool { return item.itemNum != 1 }).AnyMatch(func(item TestItem) bool { // 返回此流中是否存在 == 8的 return item.itemNum == 8 }) fmt.Println(anyMatch) noneMatch := stream.Of( TestItem{itemNum: 7, itemValue: "item7"}, TestItem{itemNum: 6, itemValue: "item6"}, TestItem{itemNum: 8, itemValue: "item8"}, TestItem{itemNum: 1, itemValue: "item1"}, ).Filter(func(item TestItem) bool { return item.itemNum != 1 }).NoneMatch(func(item TestItem) bool { // 返回此流中是否全部不等于8 return item.itemNum == 8 }) fmt.Println(noneMatch) resFirst := stream.Of( TestItem{itemNum: 1, itemValue: "item1"}, TestItem{itemNum: 2, itemValue: "item2"}, TestItem{itemNum: 3, itemValue: "item3"}, ).FindFirst() fmt.Println(resFirst.Get()) resLast := stream.Of( TestItem{itemNum: 1, itemValue: "item1"}, TestItem{itemNum: 2, itemValue: "item2"}, TestItem{itemNum: 3, itemValue: "item3"}, ).FindLast() fmt.Println(resLast.Get()) } ``` 结果: ``` false true false {1 item1} true {3 item3} true ``` ### Map、FlatMap Map与FlatMap都是用于转换已有的元素为其它元素,区别点在于: 1. Map 按照条件将已有元素转换为另一个对象类型,一对一逻辑 2. FlatMap 按照条件将已有元素转换为另一个对象类型,一对多逻辑 比如我要把 int 1 转为 TestItem{itemNum: 1, itemValue: "item1"} ```go package todocoder func TestMap(t *testing.T) { res := stream.Of([]int{1, 2, 3, 4, 7}...).Map(func(item int) any { return TestItem{ itemNum: item, itemValue: fmt.Sprintf("item%d", item), } }).Collect(collectors.ToSlice[any]()) fmt.Println(res) } ``` ``` [{1 item1} {2 item2} {3 item3} {4 item4} {7 item7}] ``` 那如果我要把两个字符串["wo shi todocoder","ha ha ha"] 转为 ["wo","shi","todocoder","ha","ha","ha"] 用Map就不行了,这就需要用到FlatMap了 ```go package todocoder func TestFlatMap(t *testing.T) { // 把两个字符串["wo shi todocoder","ha ha ha"] 转为 ["wo","shi","todocoder","ha","ha","ha"] res := stream.Of([]string{"wo shi todocoder", "ha ha ha"}...).FlatMap(func(s string) stream.Stream[any] { return stream.OfFrom(func(source chan<- any) { for _, str := range strings.Split(s, " ") { source <- str } }) }).Collect(collectors.ToSlice[any]()) fmt.Println(res) } ``` ``` [wo shi todocoder ha ha ha] ``` >   这里需要补充一句,只要经过Map或者FlatMap 处理后,类型就会统一变成 `any`了,而不是 泛型`T`,如需要强制类型处理,需要手动转换一下 >   这个原因是Go泛型的局限性导致的,不能在struct 方法中定义其他类型的泛型,这块看后续官方是否支持了 > > 可以看如下代码 ```go package todocoder func TestMap(t *testing.T) { res := stream.Of( TestItem{itemNum: 3, itemValue: "item3"}, ).FlatMap(func(item TestItem) stream.Stream[any] { return Of[any]( TestItem{itemNum: item.itemNum * 10, itemValue: fmt.Sprintf("%s+%d", item.itemValue, item.itemNum)}, TestItem{itemNum: item.itemNum * 20, itemValue: fmt.Sprintf("%s+%d", item.itemValue, item.itemNum)}, ) }).Map(func(item any) any { // 这里需要类型转换 ite := item.(TestItem) return ToTestItem{ itemNum: ite.itemNum, itemValue: ite.itemValue, } }).Collect(collectors.ToSlice[any]()) fmt.Println(res) } ``` ### collectors.ToMap、collectors.GroupBy   这两个是相对复杂的终止方法,`ToMap` 是类似于Java stream流中`Collectors.toMap()`可以把切片数组转换成 切片map, `GroupBy` 类似于Java stream中 `Collectors.groupingby()`方法,按某个维度来分组 我有如下切片列表: *TestItem{itemNum: 1, itemValue: "item1"},* *TestItem{itemNum: 2, itemValue: "item2"},* *TestItem{itemNum: 2, itemValue: "item3"}* 1. 第一个需求是:把这个列表按 itemNum为Key, itemValue 为 value转换成Map 2. 第二个需求是:把这个列表按 itemNum为Key, 分组后转换成Map 我们看一下代码: ```go package todocoder func TestToMap(t *testing.T) { // 第一个需求 resMap := Of( TestItem{itemNum: 1, itemValue: "item1"}, TestItem{itemNum: 2, itemValue: "item2"}, TestItem{itemNum: 2, itemValue: "item3"}, ).Collect(collectors.ToMap[TestItem](func(t TestItem) any { return t.itemNum }, func(item TestItem) any { return item.itemValue }, func(oldV, newV any) any { return oldV })) fmt.Println("第一个需求:") fmt.Println(resMap) // 第二个需求 resGroup := Of( TestItem{itemNum: 1, itemValue: "item1"}, TestItem{itemNum: 2, itemValue: "item2"}, TestItem{itemNum: 2, itemValue: "item3"}, ).Collect(collectors.GroupingBy(func(t TestItem) any { return t.itemNum }, func(t TestItem) any { return t })) fmt.Println("第二个需求:") fmt.Println(resGroup) } ``` ``` 第一个需求: map[1:item1 2:item2] 第二个需求: map[1:[{1 item1}] 2:[{2 item2} {2 item3}]] ``` ## 最后   作为一个Java开发,用习惯了Stream操作,也没找到合适的轻量的stream框架,也不知道后续官方是否会出,在这之前,就先自己简单实现一个,后面遇到复杂的处理流程会持续的更新到上面 除了上面这些功能,还有并行流处理,有兴趣可以自行查看体验[测试类:stream_test](https://github.com/todocoder/go-stream/blob/master/stream/stream_test.go) 有什么问题可以留言,看到后第一时间回复