前语
协程系列文章:
- 一个小故事讲了解进程、线程、Kotlin 协程究竟啥联系?
- 少年,你可知 Kotlin 协程最初的样子?
- 讲真,Kotlin 协程的挂起/恢复没那么奥秘(故事篇)
- 讲真,Kotlin 协程的挂起/恢复没那么奥秘(原理篇)
- Kotlin 协程调度切换线程是时分解开真相了
- Kotlin 协程之线程池探索之旅(与Java线程池PK)
- Kotlin 协程之取消与反常处理探索之旅(上)
- Kotlin 协程之取消与反常处理探索之旅(下)
- 来,跟我一同撸Kotlin runBlocking/launch/join/async/delay 原理&运用
- 持续来,同我一同撸Kotlin Channel 深水区
- Kotlin 协程 Select:看我怎么多路复用
- Kotlin Sequence 是时分派上用场了
- Kotlin Flow啊,你将流向何方?
- Kotlin Flow 背压和线程切换竟然如此类似
- Kotlin SharedFlow&StateFlow 暖流究竟有多热?
在进入Flow世界之前,先来剖析Sequence,从而天然延伸到Flow。
经过本篇文章,你将了解到:
- Java与Kotlin 对调集的处理
- Java Stream 的简略运用
- Sequence 的简略运用
- Sequence 的原理
- Sequence 的优劣势
1. Java与Kotlin 对调集的处理
场景剖析
客户有个场景想考验一下Java和Kotlin:
从一堆数据里(0–10000000)找到大于1000的偶数的个数。
Java和Kotlin 均表明so easy,摩拳擦掌。
秉着尊老爱幼的优良传统,老大哥Java先进场。
Java 进场
public List<Integer> dealCollection() {
List<Integer> evenList = new ArrayList<>();
for (Integer integer : list) {
//筛选出偶数
if (integer % 2 == 0) {
evenList.add(integer);
}
}
List<Integer> bigList = new ArrayList<>();
for (Integer integer : evenList) {
//从偶数中筛选出大于1000的数
if (integer > 1000) {
bigList.add(integer);
}
}
//返回筛选成果列表
return bigList;
}
Java解说说:“先将偶数的成果保存到列表里,再从偶数列表里筛选出大于1000的数。”
Kotlin 进场
Kotlin 看到Java的解决计划,表明写法有点冗余,不够灵活,所以拿出自己的计划:
fun testCollection() {
var time = measureTimeMillis {
var list = (0..10000000).filter {
it % 2 == 0
}.filter {
it > 1000
}
println("kotlin collection list size:${list.size}")
}
println("kotlin collection use time:$time")
}
Kotlin 说:“老大哥,看看我这个写法,只需求几行代码,简练如斯。”
Java 淡定到:“的确够简练,但是表面的简练掩盖了背后的许多冗余,能一层一层剥开你的心吗?”
Kotlin道:“你我热诚相对,士为知己者死,刀来!”
Java赶紧递上自己随身携带的水果刀…
Kotlin 反编译
遇事不决反编译:
public final void testCollection() {
//结构迭代器
Iterable $this$filter$iv = (Iterable)(new IntRange(var8, 10000000));
//结构链表用来存储偶数
Collection destination$iv$iv = (Collection)(new ArrayList());
//取出迭代器
Iterator var13 = $this$filter$iv.iterator();
//遍历取出偶数
while(var13.hasNext()) {
element$iv$iv = var13.next();
it = ((Number)element$iv$iv).intValue();
var16 = false;
if (it % 2 == 0) {
destination$iv$iv.add(element$iv$iv);
}
}
$this$filter$iv = (Iterable)((List)destination$iv$iv);
$i$f$filter = false;
//结构链表用来存储>1000的偶数
destination$iv$iv = (Collection)(new ArrayList());
$i$f$filterTo = false;
//取出迭代器
var13 = $this$filter$iv.iterator();
//遍历链表
while(var13.hasNext()) {
element$iv$iv = var13.next();
it = ((Number)element$iv$iv).intValue();
var16 = false;
if (it > 1000) {
destination$iv$iv.add(element$iv$iv);
}
}
//终究的成果
List list = (List)destination$iv$iv;
}
看到这,Java茅塞顿开到:“原来如此,你也是分步存储成果,我俩想到一同了,真机敏啊。”
Kotlin:“彼此彼此。”
客户说:“你俩就不要商业互吹了,我就想要一个成果而已,你们就给我弄了两个循环,若是此刻我再加一两个条件,你们是不是得要再加几个循环遍历?那不是白白添加耗时吗?”
Java 奥秘的笑道:“非也非也,我好歹也是沉浸代码界几十年的存在,早有预案。”
客户说:“那就开端你的表演吧…”
2. Java Stream 的简略运用
什么是流
Java说:“我从Java8开端就支持Stream(流) API了,能够满足你的需求。”
客户不解道:“什么是流?”
Java:“流便是一个进程,比方说你之前的需求就能够作为一个流,能够在半途对流做一系列的处理,然后在流的末尾取出处理后的成果,这个成果便是终究的成果。”
Kotlin补充道:“老大哥,你说的比较抽象,我举个比方吧。”
在一个管道的入口处放入了各种鱼,如草鱼、鲤鱼、鲢鱼、金鱼等,管道答应接入不同的小管道用以筛选不同组合的鱼类。
比方有个客户只想要金鱼,所以它分别接了4个小管道,第一个管道用来将草鱼分流,第二个管道用来分流鲤鱼,第三个管道用来分流鲢鱼,终究剩余的便是金鱼。
当然,他也能够只分流草鱼,剩余的鲤鱼、鲢鱼、金鱼他都需求,这就添加了操作的灵活性。
客户说:“talk is cheap, show me the code。”
Java Stream
Java 撸起袖子,几个呼吸之间就写好了如下代码:
public long dealCollectionWithStream() {
Stream<Integer> stream = list.stream();
return stream.filter(value -> value % 2 == 0)
.filter(value -> value > 1000)
.count();
}
客户不解地问:“这的确很简练了,但是和Kotlin写法一样的嘛?”
Java道:“No No No,别被简练的表面迷惑了,咱们直接来看看处理的耗时即可。”
public static void main(String args[]) {
Java8Stream java8Stream = new Java8Stream();
//一般调集耗时
long startTime = System.currentTimeMillis();
List<Integer> list = java8Stream.dealCollection();
System.out.println("java7 list size:" + list.size() + " use time:" + (System.currentTimeMillis() - startTime) + "ms");
//Stream API 的耗时
long startTime2 = System.currentTimeMillis();
long count = java8Stream.dealCollectionWithStream();
System.out.println("java8 stream list size:" + count + " use time:" + (System.currentTimeMillis() - startTime2) + "ms");
}
打印成果如下:
Java 持续解说:“既然只关心终究的成果,那么关于流来说,能够在各个位置指定条件对流的内容进行筛选,关于同一个内容来说只有上一个条件满足了,才会持续处理下一个条件,不然将会处理流里的其它内容。如此一来,再也不必重复存取中心成果了,关于大批量的数据来说,大大削减了耗时。”
客户赞赏:“不错,能解决我的痛点。”
Java 说:“不仅如此,我还能够并行操作流,终究将成果汇总,又能够削减一些耗时了。”
客户:“优秀,那我就选…”
Kotlin 急道:“住口…不,等等,我有话说。”
客户:“你快说,说不出子丑寅卯,我就…”
3. Sequence 的简略运用
Sequence 引入
Kotlin:“和Java老大哥一样,我也能够对流进行操作,主要是用sequence完成”
fun testSequence() {
var time = measureTimeMillis {
val count = (0..10000000)
.asSequence()//转换为sequence
.filter {
it % 2 == 0//过滤偶数
}.filter {
it > 1000//过滤>1000
}.count() //统计个数
println("kotlin sequence list size:${count}")
}
println("kotlin sequence use time:$time")
}
和未运用sequence 比照耗时:
public static void main(String args[]) {
SequenceDemo sequenceDemo = new SequenceDemo();
//运用调集操作
sequenceDemo.testCollection();
//运用sequence操作
sequenceDemo.testSequence();
}
能够看出,运用了sequence后,能够大大削减耗时。
Kotlin 反编译Sequence
4. Sequence 的原理
调集转Sequence
(0..10000000)
这表明的是0到10000000的调集,它的完成类是:
IntRange 里界说了调集的开端值和完毕值,重点在其父类:IntProgression。
IntProgression 完成了Iterable接口,并完成了该接口里的唯一办法:iterator()
详细完成类为:
internal class IntProgressionIterator(first: Int, last: Int, val step: Int) : IntIterator() {
private val finalElement: Int = last
private var hasNext: Boolean = if (step > 0) first <= last else first >= last
private var next: Int = if (hasNext) first else finalElement
override fun hasNext(): Boolean = hasNext
override fun nextInt(): Int {
val value = next
if (value == finalElement) {
if (!hasNext) throw kotlin.NoSuchElementException()
hasNext = false
}
else {
next += step
}
return value
}
}
通常来说,迭代器有三个重要元素:
- 起始值
- 步长
- 完毕值
对应的两个中心办法:
- 检测是否还有下个元素
- 取出下个元素
关于当时的Int迭代器来说:它的起始值为0,步长是1,完毕值是10000000,当咱们调用迭代器时就能够取出里面的每个数。
迭代器有了,接下来看看怎么结构为一个Sequence。
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
//取当时的迭代器,也便是IntProgressionIterator
return Sequence { this.iterator() }
}
//结构一个Sequence
public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> {
override fun iterator(): Iterator<T> = iterator()
}
Sequence 是个接口,它的唯一接口是:
public interface Sequence<out T> {
public operator fun iterator(): Iterator<T>
}
结合两者剖析可知:
asSequence() 结构了Sequence匿名内部类目标,而其完成的办法便是iterator(),该办法终究返回IntProgressionIterator 目标 也便是说Sequence初始迭代器即为Collection的迭代器
Sequence中心操作符
以filter为例:
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
//结构Sequence 子类,该子类用来过滤流
return FilteringSequence(this, true, predicate)
}
override fun iterator(): Iterator<T> = object : Iterator<T> {
//上一个Sequence的迭代器
val iterator = sequence.iterator()
var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue
var nextItem: T? = null
private fun calcNext() {
//先判别上一个Sequence的迭代器
while (iterator.hasNext()) {
val item = iterator.next()
//拿到值后判别本Sequence的逻辑
//是否契合过滤条件,契合就取出值,交个下一个条件,不契合则找下一个元素
if (predicate(item) == sendWhen) {
nextItem = item
nextState = 1
return
}
}
nextState = 0
}
//重写next()与hasNext(),里面调用了calcNext
override fun next(): T {
if (nextState == -1)
calcNext()
if (nextState == 0)
throw NoSuchElementException()
val result = nextItem
nextItem = null
nextState = -1
@Suppress("UNCHECKED_CAST")
return result as T
}
override fun hasNext(): Boolean {
if (nextState == -1)
calcNext()
return nextState == 1
}
}
咱们调用了两次filter操作符,终究构成的结构如下:
此处用到了设计形式里的装饰形式:
- Sequence 只有一般的迭代功用,现在需求为它增强过滤偶数的功用,因而新建了FilteringSequence 目标A,并持有Sequence目标,当需求调用过滤偶数的功用时,先凭借Sequence获取基本数据,再运用FilteringSequenceA过滤偶数
- 同样的,还需求在1的基础上持续增强FilteringSequence的过滤功用,再新建FilteringSequence B持有FilteringSequence目标A A,当需求调用过滤>1000的数时,先凭借FilteringSequence 目标A获取偶数,再运用FilteringSequenceB过滤>1000的数
如此一来,经过嵌套调用就完成了许多操作进程。
Sequence终端操作符
你可能现已发现了:中心操作符仅仅只是建立了装饰(引证)联系,并没有触发迭代啊,那什么时分触发迭代呢?
这个时分就需求用到终端操作符(也叫结尾操作符)。
比方count办法:
public fun <T> Sequence<T>.count(): Int {
var count = 0
//触发遍历,统计个数
for (element in this) checkCountOverflow(++count)
return count
}
当调用了count()办法后,将会触发遍历,终究调用栈如下:
只有调用了终端操作符,流才会动起来,这也便是为啥说Sequence、Java Stream 中心操作符是慵懒操作符的原因。
Sequence与一般调集链式调用区别
还是之前的Demo
一般调集链式调用
每次操作(如filter)都需求遍历调集找到契合条件的条目加入到新的调集,然后再在新的调集基础上再次进行操作。
如上图,先履行紫色区块,再履行蓝色区块。
Sequence 调用
每次先对某个条目进行一切的操作(比方filter),先判别每一步该条目是否契合,不契合则再找下一个条目进行一切的操作。
如上图:从左到右按次序履行紫色区块。
5. Sequence 的优劣势
与一般调集链式调用比照,Sequence也有链式调用。
前者链式调用每次都需求完整遍历调集并将中心成果缓存,下一次调用依靠上一次调用缓存的成果。
然后者链式调用先是将每个操作关联起来,然后当触发终端操作符时针对每一个条目(元素)先履行一切的操作(这些操作在上一步现已关联)。
由此可见,如果调集里元素许多,Sequence能够大大节省时刻(没有屡次遍历,没有暂存成果)
除此之外,Sequence 只做最少的操作,尽可能地节省时刻。
怎样了解呢?还是上面的比方,咱们只想取前10个偶数,代码如下:
fun testSequence1() {
var time = measureTimeMillis {
val count = (0..10000000)
.asSequence()//转换为sequence
.filter {
it % 2 == 0//过滤偶数
}.take(10).count()
println("kotlin sequence1 list size:${count}")
}
println("kotlin sequence1 use time:$time")
}
该序列只会履行到调集里的条目=18就停止了,由于到了0~18现已有10个偶数了,而剩余的一堆条目都无需履行,大大节省了时刻。
因而,Sequence 优势:
- 不暂存数据,不进行屡次遍历
- 只做最少的操作
- 能够产生无限的序列
以上以filter/take/count 操作符论述了Sequence的原理及其优势,当然还有更多的详细的运用场景/方式待发掘。
此刻,Kotlin 刻不容缓跳出来说:“怎样样,我这个Sequence 6吧?”
客户说:“看你字多的份上,我选择信你,那我就选…”
Java急忙道:“我有问题,我的Stream支持并行,你支持吗?”
Kotlin:“…”
想了一会儿,Kotlin持续道:“Sequence 虽然不支持切换线程,但是它的兄弟支持,它便是Flow。”
Java补充说:“你有Flow,我有LiveData,那我俩持续PK?”
没等Kotlin回话,客户急忙道:“哎哎,行了,时刻不够了,下次再持续吧,闭会…”
Java:“…”
Kotlin:“…”
第100篇博客,不忘初心,砥砺前行,持续输出高质量、成体系的博客。
下次将会进入Flow的世界。
本文基于Kotlin 1.5.3,文中完整Demo请点击
您若喜爱,请点赞、关注、保藏,您的鼓舞是我前进的动力
持续更新中,和我一同步步为营系统、深化学习Android/Kotlin
1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真了解了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 完全厘清
7、Android Window 怎么确认大小/onMeasure()屡次履行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标完全明了
11、Android Activity/Window/View 的background
12、Android Activity创立到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑问
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易学易懂系列
19、Kotlin 轻松入门系列
20、Kotlin 协程系列全面解读