Kotlin中的惰性操作容器Sequence序列使用原理詳解
Sequence序列
Sequence 是Kotlin標(biāo)準(zhǔn)庫提供的一種容器類型。它和Iterable一樣具備對(duì)集合進(jìn)行多步驟操作能力,但是卻是采用了一種完全不同于Iterable的實(shí)現(xiàn)方式:
val map = (0..3).filter { println("filter:$it") it % 2 == 0 }.map { println("map:$it") it + 1 } println(map)
上面的代碼用來演示Iterable進(jìn)行連續(xù)操作的情況。它的輸出如下:
filter:0
filter:1
filter:2
filter:3
map:0
map:2
[1, 3]
像map
和filter
這些鏈?zhǔn)郊虾瘮?shù)它們都會(huì)立即執(zhí)行并創(chuàng)建中間臨時(shí)集用來保存數(shù)據(jù)。當(dāng)原始數(shù)據(jù)不多時(shí),這并不會(huì)有什么影響。但是,當(dāng)原始數(shù)據(jù)量非常大的時(shí)候。這就會(huì)變的非常低效。而此時(shí),就可以借助Sequence
提高效率。
val sequence = (0..3).asSequence().filter { println("filter:$it") it % 2 == 0 }.map { println("map:$it") it + 1 } println("準(zhǔn)備開始執(zhí)行") println(sequence.toList())
上面的代碼執(zhí)行結(jié)果如下:
準(zhǔn)備開始執(zhí)行
filter:0
map:0
filter:1
filter:2
map:2
filter:3
[1, 3]
對(duì)比Iterable和Sequence:
Iterable是即時(shí)的、Sequence是惰性的:前者會(huì)要求盡早的計(jì)算結(jié)果,因此在多步驟處理鏈的每一環(huán)都會(huì)有中間產(chǎn)物也就是新的集合產(chǎn)生;后者會(huì)盡可能的延遲計(jì)算結(jié)果,Sequence處理的中間函數(shù)不進(jìn)行任何計(jì)算。相反,他們返回一個(gè)新Sequence的,用新的操作裝飾前一個(gè),所有的這些計(jì)算都只是在類似toList的終端操作期間進(jìn)行。
區(qū)分中間操作符和末端操作符的方式也很簡(jiǎn)單:如果操作符返回的是一個(gè)Sequence類型的數(shù)據(jù),它就是中間操作符。
在操作的執(zhí)行方式上也有所不同:Iterable每次都是在整個(gè)集合執(zhí)行完操作后再進(jìn)行下一步操作——采用第一個(gè)操作并將其應(yīng)用于整個(gè)集合,然后移動(dòng)到下一個(gè)操作,官方將其稱呼為急切式或者按步驟執(zhí)行(Eager/step-by-step);**而Sequence則是逐個(gè)對(duì)每個(gè)元素執(zhí)行所有操作。是一種惰性順序——取第一個(gè)元素并應(yīng)用所有操作,然后取下一個(gè)元素,依此類推。**官方將其稱呼為惰性式或者按元素執(zhí)行(Lazy/element-by-element)
序列的惰性會(huì)帶來一下幾個(gè)優(yōu)點(diǎn):
- 它們的操作按照元素的自然順序進(jìn)行;
- 只做最少的操作;
- 元素可以是無限多個(gè);
- 不需要在每一步都創(chuàng)建集合
Sequence可避免生成中間步驟的結(jié)果,從而提高了整個(gè)集合處理鏈的性能。但是,惰性性質(zhì)也會(huì)帶來一些運(yùn)行開銷。所以在使用時(shí)要權(quán)衡惰性開銷和中間步驟開銷,在Sequence和Iterable中選擇更加合適的實(shí)現(xiàn)方式。
執(zhí)行的順序
sequenceOf(1,2,3) .filter { print("F$it, "); it % 2 == 1 } .map { print("M$it, "); it * 2 } .forEach { print("E$it, ") } // Prints: F1, M1, E2, F2, F3, M3, E6, listOf(1,2,3) .filter { print("F$it, "); it % 2 == 1 } .map { print("M$it, "); it * 2 } .forEach { print("E$it, ") } // Prints: F1, F2, F3, M1, M3, E2, E6,
sequence的執(zhí)行時(shí)按照元素進(jìn)行的,依次對(duì)元素執(zhí)行所有的操作,對(duì)一個(gè)元素而言,所有操作時(shí)依次全部執(zhí)行的。而普通集合操作則是以操作步驟進(jìn)行的,當(dāng)所有的元素執(zhí)行完當(dāng)前操作后才會(huì)進(jìn)入下一個(gè)操作。
只做最少的操作
試想一下我們有一千萬個(gè)數(shù)字,我們要經(jīng)過幾次變換取出20個(gè),使用下面的代碼對(duì)比一下序列和不同集合操作的性能:
fun main(){ val fFlow = FFlow() fFlow.demoList() fFlow.demoSequence() } fun demoSequence() { val currentTimeMillis = System.currentTimeMillis() val list = (0..10000000).asSequence().map { it * 2 }.map { it - 1 }.take(20).toList() println("demoSequence:${System.currentTimeMillis() - currentTimeMillis}:$list") } fun demoList() { val currentTimeMillis = System.currentTimeMillis() val list = (0..10000000).map { it * 2 }.map { it - 1 }.take(20).toList() println("demoList:${System.currentTimeMillis() - currentTimeMillis}:$list") }
輸出的結(jié)果如下:
demoSequence:20ms:[-1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37]
demoList:4106ms:[-1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37]
這就是只執(zhí)行最少操作的意思,序列按照元素順序執(zhí)行,當(dāng)取夠29個(gè)元素之后便會(huì)立即停止計(jì)算。而不同的集合則不同,沒有中間操作的概念。它的每次操作都會(huì)對(duì)整個(gè)數(shù)組中的所有元素執(zhí)行完才會(huì)進(jìn)行下一個(gè)——也就是前兩個(gè)map都要執(zhí)行一千萬次。
序列可以是無限的
看如下代碼:
var list = emptyArray<Int>() var i = 0 while(true){ list[i] = i++ } list.take(10)
很明顯,這段代碼是沒法正常運(yùn)行的,因?yàn)檫@里有一個(gè)死循環(huán)。我們也無法創(chuàng)建一個(gè)無限長(zhǎng)度的集合。但是:因?yàn)樾蛄惺桨床襟E依照需求進(jìn)行處理的,所喲我們可以創(chuàng)建無限序列:
val noEnd = sequence { var i = 1 while (true) { yield(i) i *= 2 } } noEnd.take(4).toList() //輸出:[1, 2, 4, 8]
但是一定要注意,我們雖然可以這么寫,但是務(wù)必不能真的讓while一直循環(huán)。我們不能直接使用toList。必須提供一個(gè)能結(jié)束循環(huán)的操作符,也就是不能取出所有元素(無限個(gè))——要么使用類似take的操作來限制它們的數(shù)量,要么使用不需要所有元素的終端操作,例如first, find, any, all, indexOf等。
序列不會(huì)在每個(gè)步驟創(chuàng)建集合
普通的集合會(huì)在每次變換之后都會(huì)創(chuàng)建新的集合取存儲(chǔ)所有變換后的元素。而每次創(chuàng)建集合和填入數(shù)據(jù)都會(huì)帶來不小的性能開銷。尤其是當(dāng)我們處理大量或大量的集合時(shí),性能問題會(huì)愈發(fā)凸顯。而序列的按元素操作,則不會(huì)有這個(gè)問題。除非手動(dòng)調(diào)用了終端操作符,否則不會(huì)生成新的集合。
Sequence的基本使用
Sequence序列的使用和普通的Iterable極其相似,實(shí)際上其內(nèi)部也還是借助Iterable實(shí)現(xiàn)的。在研究它的內(nèi)部實(shí)現(xiàn)原理之前,想從Sequence的創(chuàng)建和基本的序列操作來演示Sequence的基本用法。
序列的創(chuàng)建
創(chuàng)建Sequence的方式大概可以分為。分別是由元素創(chuàng)建、通過Iterable、借助函數(shù)以及由代碼塊創(chuàng)建。
由元素創(chuàng)建:通過調(diào)用頂級(jí)函數(shù)sequenceOf
實(shí)現(xiàn):
val ints = sequenceOf(1, 2, 3, 4, 5, 6, 7) val strings = sequenceOf("a","b","c","d","e")
通過Iterable轉(zhuǎn)化:借助Iterable的擴(kuò)展函數(shù)asSequence
實(shí)現(xiàn):
val ints = listOf(1, 2, 3, 4, 5, 6, 7).asSequence() val strings = listOf("a","b","c","d","e").asSequence()
通過generateSequence實(shí)現(xiàn):該方法有三個(gè):
generateSequence(seedFunction: () -> T?, nextFunction: (T) -> T?): Sequence<T> generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> generateSequence(nextFunction: () -> T?): Sequence<T>
最終都是通過GeneratorSequence
實(shí)現(xiàn)的,這里先不進(jìn)行源碼分析。只討論使用方式:
- 其中三個(gè)函數(shù)都有的形參nextFunction可以理解為元素生成器,序列里的元素都通過調(diào)用該函數(shù)生成,當(dāng)它返回為null是,序列停止生成(所以,nextFunction必須要在某個(gè)情況下返回null,否則會(huì)因?yàn)樾蛄性厥菬o限多個(gè)觸發(fā)java.lang.OutOfMemoryError: Java heap space異常)。
- 而另外兩個(gè)的seedFunction和seed形參都是為了確定數(shù)據(jù)初始值的。區(qū)別在于一個(gè)直接指明,一個(gè)通過調(diào)用函數(shù)獲取。
分別用這三個(gè)函數(shù)生成0~100的序列,代碼如下:
val generateSequenceOne = generateSequence { if (i < 100) { i++ } else { null } } val generateSequenceTwo = generateSequence(0) { if (it < 100) { it+1//此處的it是上一個(gè)元素 } else { null } } val generateSequenceThree = generateSequence({ 0 }) { if (it < 100) { it+1//此處的it是上一個(gè)元素 } else { null } }
由代碼塊生成:借助sequence(block: suspend SequenceScope.() -> Unit)函數(shù)。該函數(shù)接受一個(gè)掛起函數(shù),該函數(shù)會(huì)接受一個(gè)SequenceScope
實(shí)例,這個(gè)實(shí)例無需我們創(chuàng)建(后面源碼分析會(huì)講到)。SequenceScope提供了yield
和yieldAll
方法復(fù)雜返回序列元素給調(diào)用者,并暫停序列的執(zhí)行,直到使用者請(qǐng)求下一個(gè)元素。
用該函數(shù)生成0~100的序列,代碼如下:
val ints = sequence { repeat(100) { yield(it) } }
序列的操作
對(duì)序列的操作可以分為中間操作和末端操作兩種。它們只要有一下另種區(qū)別:
- 中間操作返回惰性生成的一個(gè)新的序列,而末端序列則為其他普通的數(shù)據(jù)類型;
- 中間操作不會(huì)立刻執(zhí)行代碼,僅當(dāng)執(zhí)行了末端操作序列才會(huì)開始執(zhí)行。
常見的中間操作包括:map、fliter、first、last、take等;它們會(huì)序列提供數(shù)據(jù)變化過濾等增強(qiáng)功能基本上和kotlin提供的集合操作符有著相同的功能。
常見的末端操作有:toList、toMutableList、sum、count等。它們?cè)谔峁┬蛄胁僮鞴δ艿耐瑫r(shí),還會(huì)觸發(fā)序列的運(yùn)行。
Sequence源碼分析
上文對(duì)序列做了簡(jiǎn)單的入門介紹。接下來深入源碼去了解一下Sequence的實(shí)現(xiàn)方式。
Sequence是什么?
Kotlin對(duì)的定義Sequence很簡(jiǎn)單:
public interface Sequence <out T> { public operator fun iterator(): Iterator<T> }
就是一個(gè)接口,定義了一個(gè)返回Iterator的方法。接口本身只定義了Sequence具有返回一個(gè)迭代器的能力。具體的功能實(shí)現(xiàn)還是靠它的實(shí)現(xiàn)類完成。
可以概括一些:序列就是一個(gè)具備提供了迭代器能力的類。
序列的創(chuàng)建方式分析
結(jié)合上文中提到的序列的四種創(chuàng)建方式,我們依次分析一下它的創(chuàng)建流程。
我們首先以比較常用的通過Iterable轉(zhuǎn)化獲取序列,它需要借助asSequence
方法分析一下,使用listOf("a","b","c","d","e").asSequence()
生成一個(gè)序列。調(diào)用鏈如下:
public fun <T> Iterable<T>.asSequence(): Sequence<T> { return Sequence { this.iterator() } } public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> { override fun iterator(): Iterator<T> = iterator() }
流程很簡(jiǎn)單,一個(gè)擴(kuò)展函數(shù)加一個(gè)內(nèi)聯(lián)函數(shù)。最終通過匿名內(nèi)部類的方式創(chuàng)建一個(gè)Sequence并返回。代碼很好理解,實(shí)際上它的實(shí)現(xiàn)邏輯等同于下面的代碼:
val sequence = MySequence(listOf("a","b","c","d","e").iterator()) class MySequence<T>(private val iterator:Iterator<T>):Sequence<T>{ override fun iterator(): Iterator<T> { return iterator } }
接著看一下通過調(diào)用頂級(jí)函數(shù)sequenceOf
實(shí)現(xiàn),以sequenceOf("a","b","c","d","e")
為例,它的調(diào)用邏輯如下:
public fun <T> sequenceOf(vararg elements: T): Sequence<T> = if (elements.isEmpty()) emptySequence() else elements.asSequence()
可以看到依舊是借助asSequence實(shí)現(xiàn)的。
接下來看一下代碼塊和generateSequence的實(shí)現(xiàn)方式,這兩個(gè)方式會(huì)比較復(fù)雜一點(diǎn),畢竟前面兩個(gè)都是借助List進(jìn)行轉(zhuǎn)換,而List本身就能提供迭代器Iterator。后面兩個(gè)明顯需要提供新的迭代器。 首先看一下代碼看的實(shí)現(xiàn)方式:
val ints = sequence { repeat(100) { yield(it) } }
其中sequence的調(diào)用邏輯如下:
public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) } public fun <T> iterator(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Iterator<T> { //創(chuàng)建迭代器 val iterator = SequenceBuilderIterator<T>() iterator.nextStep = block.createCoroutineUnintercepted(receiver = iterator, completion = iterator) return iterator } public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> { override fun iterator(): Iterator<T> = iterator() }
可以發(fā)現(xiàn):該方法和asSequence一樣最終也是通過匿名內(nèi)部類的方式創(chuàng)建了一個(gè)Sequence。不過區(qū)別在于,該方法需要?jiǎng)?chuàng)建一個(gè)新的迭代器,也就是SequenceBuilderIterator
。同樣以MySequence為例,它的創(chuàng)建流程等同于一下代碼:
fun mian(){ create<Int> { myblock() } } suspend fun SequenceScope<Int>.myblock(){ repeat(100) { yield(it) } } fun <Int> create(block: suspend SequenceScope<Int>.() -> Unit):Sequence<Int>{ val iterator = SequenceBuilderIterator<Int>() iterator.nextStep = block.createCoroutineUnintercepted(receiver = iterator, completion = iterator) return MySequence(iterator) }
當(dāng)然,這是不可能實(shí)現(xiàn)的,因?yàn)镾equenceBuilderIterator是被private修飾了,我們是無法直接訪問的。這里強(qiáng)制寫出來演示一下它的流程。
最后看一下通過generateSequence方法創(chuàng)建序列的源碼,一共有三個(gè):
public fun <T : Any> generateSequence(seedFunction: () -> T?, nextFunction: (T) -> T?): Sequence<T> = GeneratorSequence(seedFunction, nextFunction) public fun <T : Any> generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence<T> = if (seed == null) EmptySequence else GeneratorSequence({ seed }, nextFunction) public fun <T : Any> generateSequence(nextFunction: () -> T?): Sequence<T> { return GeneratorSequence(nextFunction, { nextFunction() }).constrainOnce() }
最終都是創(chuàng)建了GeneratorSequence
的一個(gè)實(shí)例并返回,而GeneratorSequence
實(shí)現(xiàn)了Sequence
接口并重寫了iterator()
方法:
private class GeneratorSequence<T : Any>(private val getInitialValue: () -> T?, private val getNextValue: (T) -> T?) : Sequence<T> { override fun iterator(): Iterator<T> = object : Iterator<T> { var nextItem: T? = null var nextState: Int = -2 // -2 for initial unknown, -1 for next unknown, 0 for done, 1 for continue private fun calcNext() { nextItem = if (nextState == -2) getInitialValue() else getNextValue(nextItem!!) nextState = if (nextItem == null) 0 else 1 } override fun next(): T { if (nextState < 0) calcNext() if (nextState == 0) throw NoSuchElementException() val result = nextItem as T // Do not clean nextItem (to avoid keeping reference on yielded instance) -- need to keep state for getNextValue nextState = -1 return result } override fun hasNext(): Boolean { if (nextState < 0) calcNext() return nextState == 1 } } }
總結(jié)一下Sequence的創(chuàng)建大致可以分為三類:
- 使用List自帶的迭代器通過匿名的方式創(chuàng)建Sequence實(shí)例,
sequenceOf("a","b","c","d","e")
和listOf("a","b","c","d","e").asSequence()
就是這種方式; - 創(chuàng)建新的
SequenceBuilderIterator
迭代器,并通過匿名的方式創(chuàng)建Sequence實(shí)例。例如使用代碼塊的創(chuàng)建方式。 - 創(chuàng)建
GeneratorSequence
,通過重寫iterator()方法,使用匿名的方式創(chuàng)建Iterator。GeneratorSequence方法就是采用的這種方式。
看完創(chuàng)建方式,也沒什么奇特的,就是一個(gè)提供迭代器的普通類。還是看不出是如何惰性執(zhí)行操作的。接下來就分析一下惰性操作的原理。
序列的惰性原理
以最常用的map操作符為例:普通的集合操作源碼如下:
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> { //出啊年一個(gè)新的ArrayList,并調(diào)用mapTo方法 return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform) } public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C { //遍歷原始的集合,進(jìn)行變換操作,然后將變換后的數(shù)據(jù)依次加入到新創(chuàng)建的集合 for (item in this) destination.add(transform(item)) //返回新集合 return destination }
可以看到:當(dāng)List.map被調(diào)用后,便會(huì)立即創(chuàng)建新的集合,然后遍歷老數(shù)據(jù)并進(jìn)行變換操作。最后返回一個(gè)新的數(shù)據(jù)。這印證了上面提到的普通集合的操作時(shí)按照步驟且會(huì)立刻執(zhí)行的理論。
接下來看一下序列的map方法,它的源碼如下:
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> { return TransformingSequence(this, transform) } internal class TransformingSequence<T, R> constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> { override fun iterator(): Iterator<R> = object : Iterator<R> { //注釋一:TransformingSequence的iterator持有上一個(gè)序列的迭代器 val iterator = sequence.iterator() override fun next(): R { //注釋二:在開始執(zhí)行迭代時(shí),向上調(diào)用前一個(gè)序列的迭代器。 return transformer(iterator.next()) } override fun hasNext(): Boolean { return iterator.hasNext() } } internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> { return FlatteningSequence<T, R, E>(sequence, transformer, iterator) } }
代碼并不復(fù)雜,它接收用戶提供的變換函數(shù)和序列,然后創(chuàng)建了一個(gè)TransformingSequence并返回。TransformingSequence本身和上文中提到的序列沒什么區(qū)別,唯一的區(qū)別在于它的迭代器:在通過next依次取數(shù)據(jù)的時(shí)候,并不是直接返回元素。而是先調(diào)用調(diào)用者提供的函數(shù)進(jìn)行變換。返回變換后的數(shù)據(jù)——這也沒什么新鮮的,和普通集合的map操作符和RxJava的Map都是同樣的原理。
但是,這里卻又有點(diǎn)不一樣。操作符里沒有任何開啟迭代的代碼,它只是對(duì)序列以及迭代進(jìn)行了嵌套處理,并不會(huì)開啟迭代.如果用戶不手動(dòng)調(diào)用(后者間接調(diào)用)迭代器的next函數(shù),序列就不會(huì)被執(zhí)行——這就是惰性執(zhí)行的機(jī)制的原理所在。
而且,由于操作符返回的是一個(gè)Sequence類型的值,當(dāng)你重復(fù)不斷調(diào)用map時(shí),例如下面的代碼:
(0..10).asSequence().map{add(it)}.map{add(it)}.map{add(it)}.toList() //等同于 val sequence1 = (0..10).asSequence() val sequence2 = sequence1.map { it+1 } val sequence3 = sequence2.map { it+1 } sequence3.toList()
最終,序列sequence3的結(jié)構(gòu)持有如下:sequence3-> sequence2 -> sequence1。而它們都有各自的迭代器。迭代器里都重寫了各自的變換邏輯:
override fun next(): R { return transformer(iterator.next()) } //由于這里都是執(zhí)行的+1操作,所以變換邏輯transformer可以認(rèn)為等同于如下操作: override fun next(): R { return iterator.next()+1 }
而當(dāng)我們通過sequence3.toList
執(zhí)行代碼時(shí),它的流程如下:
public fun <T> Sequence<T>.toList(): List<T> { return this.toMutableList().optimizeReadOnlyList() } public fun <T> Sequence<T>.toMutableList(): MutableList<T> { //末端操作符,此處才會(huì)開始創(chuàng)建新的集合 return toCollection(ArrayList<T>()) } public fun <T, C : MutableCollection<in T>> Sequence<T>.toCollection(destination: C): C { //執(zhí)行迭代器next操作 //當(dāng)調(diào)用(末端操作符)走到這里時(shí),便會(huì)和普通結(jié)合的操作符一樣 //此時(shí)為新創(chuàng)建的集合賦值 for (item in this) { destination.add(item) } return destination }
經(jīng)過幾次擴(kuò)展函數(shù)調(diào)用,最終在toCollection里開始執(zhí)行迭代(Iterator的典型的操作),也就是獲取了sequence3的iterator實(shí)例,并不斷通過next取出數(shù)據(jù)。而在上文中的TransformingSequence源碼里可以看到,TransformingSequence會(huì)持有上一個(gè)迭代器的實(shí)例(代碼注釋一)。
并且在迭代開始后,在進(jìn)行transformer操作(也就是執(zhí)行+1操作)前,會(huì)調(diào)用上一個(gè)迭代器的next方法進(jìn)行迭代(代碼注釋二)。這樣不斷的迭代,最終,最終會(huì)調(diào)用到sequence1的next方法。再結(jié)合上文中的序列創(chuàng)建里的分析——sequence1里所持有的迭代器就是就是原始數(shù)據(jù)里的迭代器。
那么當(dāng)最終執(zhí)行toList方法時(shí),它會(huì)循環(huán)sequence3.iterator
方法。而在每次循環(huán)內(nèi),都會(huì)首先執(zhí)行sequence3所持有的sequence2.iterator的next方法。sequence2依次類推執(zhí)行到sequence1的sequence1.iterator`方法,最終執(zhí)行到我們?cè)紨?shù)組的迭代器next方法:
整個(gè)流程如下:
原理就是這么簡(jiǎn)單:中間操作符通過序列嵌套,實(shí)現(xiàn)對(duì)迭代器iterator的嵌套。這樣在進(jìn)行迭代的時(shí)候,會(huì)依次調(diào)用各個(gè)iterator迭代器直到調(diào)用到原始集合數(shù)據(jù)里的迭代器開始并返回元素。而當(dāng)元素返回時(shí),會(huì)依次執(zhí)行各個(gè)迭代器持有變換操作方法實(shí)現(xiàn)對(duì)數(shù)據(jù)的變換。
而其他操作符,也是遵循這個(gè)基本的規(guī)則。無非就是增加一些其他的操作。
總結(jié)
- 序列通過中間操作符對(duì)迭代器進(jìn)行嵌套和復(fù)寫,以此實(shí)現(xiàn)按元素操作執(zhí)行變換;
- 中間操作符只負(fù)責(zé)根據(jù)需求創(chuàng)建并嵌套迭代器,并不負(fù)責(zé)開啟迭代器。以此實(shí)現(xiàn)惰性操作且不產(chǎn)生臨時(shí)集合;
- 末端操作符負(fù)責(zé)開啟迭代,按照嵌套順序執(zhí)行迭代操作。依次獲取操作后的數(shù)據(jù),并且會(huì)創(chuàng)建新的集合用來存儲(chǔ)最終數(shù)據(jù);
- 序列不是萬能的,因?yàn)橐胄碌膶?duì)象。在帶來惰性和順序執(zhí)行的優(yōu)勢(shì)時(shí),這些對(duì)象必然會(huì)帶來性能開銷。所以要依需求在集合和序列之間進(jìn)行選擇,使用合適的方式進(jìn)行迭代。
以上就是Kotlin中的惰性操作容器Sequence序列使用原理詳解的詳細(xì)內(nèi)容,更多關(guān)于Kotlin惰性容器Sequence的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)IM多人員組合的群組頭像
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)IM多人員組合的群組頭像,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10Android TextView的TextWatcher使用案例詳解
這篇文章主要介紹了Android TextView的TextWatcher使用案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Android FaceDetector實(shí)現(xiàn)人臉檢測(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android FaceDetector實(shí)現(xiàn)人臉檢測(cè)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05使用androidx BiometricPrompt實(shí)現(xiàn)指紋驗(yàn)證功能
這篇文章主要介紹了使用androidx BiometricPrompt實(shí)現(xiàn)指紋驗(yàn)證功能,對(duì)android指紋驗(yàn)證相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧2021-07-07關(guān)于android連續(xù)點(diǎn)擊出現(xiàn)多個(gè)Activity界面的解決方法
這篇文章主要介紹了關(guān)于android連續(xù)點(diǎn)擊出現(xiàn)多個(gè)Activity界面的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03android開發(fā)實(shí)踐之ndk編譯命令簡(jiǎn)單示例
這篇文章主要給大家介紹了在android中ndk編譯命令使用的相關(guān)資料,文中詳細(xì)介紹了ndk-build命令行參數(shù),并通過簡(jiǎn)單的示例代碼給大家介紹了如何編寫 .c 文件,需要的朋友可以參考借鑒,下面來一起看看吧。2017-06-06android開發(fā)教程之卸載sd卡對(duì)MediaServer的處理
Android中如果MediaServer訪問SD卡上的音頻文件,卸載SD卡的時(shí)候,就會(huì)kill掉MediaServer,卸載SD卡上必要條件就是沒有進(jìn)程訪問SD卡上的資源文件。Kill掉MediaServer的進(jìn)程后,MediaServer會(huì)重新啟動(dòng)。2014-02-02基于Android中獲取資源的id和url方法總結(jié)
下面小編就為大家分享一篇基于Android中獲取資源的id和url方法總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-02-02Android動(dòng)態(tài)人臉檢測(cè)的示例代碼(臉數(shù)可調(diào))
本篇文章主要介紹了Android動(dòng)態(tài)人臉檢測(cè)的示例代碼(臉數(shù)可調(diào)),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08輕松實(shí)現(xiàn)Android自定義九宮格圖案解鎖
這篇文章主要幫助大家輕松實(shí)現(xiàn)Android九宮格圖案解鎖功能,可以將圖案轉(zhuǎn)化成數(shù)字密碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11