简述: 好久没有发布原创文章,一如既往,今天开始Kotlin浅谈系列的第十讲,一起来探索Kotlin中的序列。序列(Sequences)实际上是对应Java8中的Stream的翻版。从之前博客可以了解到Kotlin定义了很多操作集合的API,没错这些函数照样适用于序列(Sequences),而且序列操作在性能方面优于集合操作.而且通过之前函数式API的源码中可以看出它们会创建很多中间集合,每个操作符都会开辟内存来存储中间数据集,然而这些在序列中就不需要。让我们一起来看看这篇博客内容:
- 1、为什么需要序列(Sequences)?
- 2、什么是序列(Sequences)?
- 3、怎么创建序列(Sequences)?
- 4、序列(Sequences)操作和集合操作性能对比
- 5、序列(Sequences)性能优化的原理
- 6、序列(Sequences)原理源码完全解析
1、为什么需要序列(Sequences)?
我们一般在Kotlin中处理数据集都是集合,以及使用集合中一些函数式操作符API,我们很少去将数据集转换成序列再去做集合操作。这是因为我们一般接触的数据量级比较小,使用集合和序列没什么差别,让我们一起来看个例子,你就会明白使用序列的意义了。
//不使用Sequences序列,使用普通的集合操作
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${
System.currentTimeMillis() - startTime}")
}
fun main(args: Array<String>) = computeRunTime {
(0..10000000)
.map {
it + 1 }
.filter {
it % 2 == 0 }
.count {
it < 10 }
.run {
println("by using list way, result is : $this")
}
}
运行结果:
//转化成Sequences序列,使用序列操作
fun computeRunTime(action: (() -> Unit)?) {
val startTime = System.currentTimeMillis()
action?.invoke()
println("the code run time is ${
System.currentTimeMillis() - startTime}")
}
fun main(args: Array<String>) = computeRunTime {
(0..10000000)
.asSequence()
.map {
it + 1 }
.filter {
it % 2 == 0 }
.count {
it < 10 }
.run {
println("by using sequences way, result is : $this")
}
}
运行结果:
通过以上同一个功能实现,使用普通集合操作和转化成序列后再做操作的运行时间差距不仅一点点,也就对应着两种实现方式在数据集量级比较大的情况下,性能差异也是很大的。这样应该知道为什么我们需要使用Sequences序列了吧。
2、什么是序列(Sequences)?
序列操作又被称之为惰性集合操作,Sequences序列接口强大在于其操作的实现方式。序列中的元素求值都是惰性的,所以可以更加高效使用序列来对数据集中的元素进行链式操作(映射、过滤、变换等),而不需要像普通集合那样,每进行一次数据操作,都必须要开辟新的内存来存储中间结果,而实际上绝大多数的数据集合操作的需求关注点在于最后的结果而不是中间的过程,
序列是在Kotlin中操作数据集的另一种选择,它和Java8中新增的Stream很像,在Java8中我们可以把一个数据集合转换成Stream,然后再对Stream进行数据操作(映射、过滤、变换等),序列(Sequences)可以说是用于优化集合在一些特殊场景下的工具。但是它不是用来替代集合,准确来说它起到是一个互补的作用。
序列操作分为两大类:
- 1、中间操作
序列的中间操作始终都是惰性的,一次中间操作返回的都是一个序列(Sequences),产生的新序列内部知道如何变换原始序列中的元素。怎样说明序列的中间操作是惰性的呢?一起来看个例子:
fun main(args: Array<String>) {
(0..6)
.asSequence()
.map {
//map返回是Sequence<T>,故它属于中间操作
println("map: $it")
return@map it + 1
}
.filter {
//filter返回是Sequence<T>,故它属于中间操作
println("filter: $it")
return@filter it % 2 == 0
}
}
运行结果:
以上例子只有中间操作没有末端操作,通过运行结果发现map、filter中并没有输出任何提示,这也就意味着map和filter的操作被延迟了,它们只有在获取结果的时候(也即是末端操作被调用的时候)才会输出提示。
- 2、末端操作
序列的末端操作会执行原来中间操作的所有延迟计算,欢聚,一次末端操作返回的是一个结果,返回的结果可以是集合、数字、或者从其他对象集合变换得到任意对象。上述例子加上末端操作:
fun main(args: Array<String>) {
(0..6)
.asSequence()
.map {
//map返回是Sequence<T>,故它属于中间操作
println("map: $it")
return@map it + 1
}
.filter {
//filter返回是Sequence<T>,故它属于中间操作
println("filter: $it")
return@filter it % 2 == 0
}
.count {
//count返回是Int,返回的是一个结果,故它属于末端操作
it < 6
}
.run {
println("result is $this");
}
}
运行结果
注意:判别是否是中间操作还是末端操作很简单,只需要看操作符API函数返回值的类型,如果返回的是一个Sequence那么这就是一个中间操作,如果返回的是一个具体的结果类型,比如Int,Boolean,或者其他任意对象,那么它就是一个末端操作
3、怎么创建序列(Sequences)?
创建序列(Sequences)的方法主要有:
- 1、使用Iterable的扩展函数asSequence来创建。
//定义声明
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
return Sequence {
this.iterator() }
}
//调用实现
list.asSequence()
- 2、使用generateSequence函数生成一个序列。
//定义声明
@kotlin