1 Star 5 Fork 2

TodoCoder / go-stream

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

阅读其他语言版本: 【English | 中文】.

Go-Stream 受到 Java 8 Streams 和 go-zero stream的启发

简介

  在JAVA中,涉及到对数组、Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行处理。那么在Go中用的多的是切片,那么这里基于Java的stream的操作习惯用Go语言( 1.18+)的泛型和通道实现了一些简单的流操作功能。

用Go-Stream 转换或者Groupby后的数据 ,可以直接使用,无需断言。

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进行指定

转换函数

   通过这几个函数你可以实现类型转换,分组,flatmap 等处理

注意:这几个函数非常有用,也是最常用的,由于Go语言泛型的局限性,Go语言方法不支持自己独立的泛型,所以导致用Stream中的方法转换只能用 interface{} 代替,这样会有个非常麻烦的问题就是,转换后用的时候必须得强转才能用,所以我把这些写成转换函数,就不会受制于类(struct) 的泛型了。

API 功能说明
Map() 类型转换(优点:和上面的Map不一样的是,这里转换后可以直接使用,不需要强转)
FlatMap() 按照条件将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流(优点:同Map)
GroupingBy() 对元素进行逐个遍历,然后执行给定的处理逻辑
Collect() 将流转换为指定的类型,通过collectors.Collector进行指定(优点:转换后的类型可以直接使用,无需强转)

go-stream的使用

库的引入

  由于用到了泛型,支持的版本为golang 1.18+

  1. go.mod 中加入如下配置
require github.com/todocoder/go-stream v1.1.0
  1. 执行
go mod tidy -go=1.20
go mod download

ForEach、Peek的使用

  ForEach和Peek都可以用于对元素进行遍历然后逐个的进行处理。 但Peek属于中间方法,而ForEach属于终止方法。也就是说Peek只能在流中间处理元素,没法直接执行得到结果,其后面必须还要有其它终止操作的时候才会被执行;而ForEach作为无返回值的终止方法,则可以直接执行相关操作。

ForEach

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中比较常用的中间处理方法,具体说明在上面已标出。使用的话我们可以在流中一个或多个的组合便用。

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

  这些方法,均属于这里说的简单结果终止方法。代码如下:

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"}

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了

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 方法中定义其他类型的泛型,这块看后续官方是否支持了

可以看如下代码

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
    我们看一下代码:
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}]]

转换函数

Map、FlatMap(无需断言)

Map与FlatMap都是用于转换已有的元素为其它元素,区别点在于:

  1. Map 按照条件将已有元素转换为另一个对象类型,一对一逻辑
  2. FlatMap 按照条件将已有元素转换为另一个对象类型,一对多逻辑

比如我要把 TestItem{itemNum: 1, itemValue: "item1"} 转换为ToTestItem{itemNum: 1, itemValue: "item1"},并且把一个元素按照一定的规则扩展成两个元素,可以通过如下的代码来实现

func TestFlatMap(t *testing.T) {
	res := stream.Map(stream.Of(
		TestItem{itemNum: 1, itemValue: "item1"},
		TestItem{itemNum: 2, itemValue: "item2"},
		TestItem{itemNum: 3, itemValue: "item3"},
	).FlatMap(func(item TestItem) Stream[TestItem] {
		return Of[TestItem](
			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)},
		)
	}), func(item TestItem) ToTestItem {
		return ToTestItem{
			itemNum:   item.itemNum,
			itemValue: item.itemValue,
		}
	}).ToSlice()
	fmt.Println(res)
}

GroupingBy() (使用结果无需断言)

需求: 班级有一组学号{1,2,3,....,12},对应12个人的信息在内存里面存着,把这学号转换成具体的 Student 类,过滤掉 Score 为 1的,并且按评分 Score 分组,并且对各组按照 Age 降序排列


studentMap := map[int]Student{
    1:  {Num: 1, Name: "小明", Score: 3, Age: 26},
    2:  {Num: 2, Name: "小红", Score: 4, Age: 27},
    3:  {Num: 3, Name: "小李", Score: 5, Age: 19},
    4:  {Num: 4, Name: "老王", Score: 1, Age: 23},
    5:  {Num: 5, Name: "小王", Score: 2, Age: 29},
    6:  {Num: 6, Name: "小绿", Score: 2, Age: 24},
    7:  {Num: 7, Name: "小蓝", Score: 3, Age: 29},
    8:  {Num: 8, Name: "小橙", Score: 3, Age: 30},
    9:  {Num: 9, Name: "小黄", Score: 4, Age: 22},
    10: {Num: 10, Name: "小黑", Score: 5, Age: 21},
    11: {Num: 11, Name: "小紫", Score: 3, Age: 32},
    12: {Num: 12, Name: "小刘", Score: 2, Age: 35},
}

res := GroupingBy(Map(Of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), func(n int) Student {
    // 注意 这里的返回类型可以是目标类型了
    return studentMap[n]
}).Filter(func(s Student) bool {
    // 这里过滤也不需要转换类型
    // 过滤掉1的
    return s.Score != 1
}), func(t Student) int {
    return t.Score
}, func(t Student) Student {
    return t
}, func(t1 []Student) {
    // 按年龄降序排列
    sort.Slice(t1, func(i, j int) bool {
        return t1[i].Age > t1[j].Age
    })
})
println(res)

最后

  作为一个Java开发,用习惯了Stream操作,也没找到合适的轻量的stream框架,也不知道后续官方是否会出,在这之前,就先自己简单实现一个,后面遇到复杂的处理流程会持续的更新到上面 除了上面这些功能,还有并行流处理,有兴趣可以自行查看体验测试类:stream_test

有什么问题可以留言,看到后第一时间回复

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

一个类似于java stream 的用golang 实现的 stream库 展开 收起
Go
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/todocoder/go-stream.git
git@gitee.com:todocoder/go-stream.git
todocoder
go-stream
go-stream
master

搜索帮助