Kotlin Coroutines Flow 系列(五) 其他的操作符 八. Flow 其他的操作符 九. Flow VS Reactive Streams

八. Flow 其他的操作符

8.1 Transform operators

transform

在使用 transform 操作符時,可以任意多次調用 emit ,這是 transform 跟 map 最大的區別:

fun main() = runBlocking {

    (1..5).asFlow()
        .transform {
            emit(it * 2)
            delay(100)
            emit(it * 4)
        }
        .collect { println(it) }
}

transform 也可以使用 emit 發射任意值:

fun main() = runBlocking {

    (1..5).asFlow()
        .transform {
            emit(it * 2)
            delay(100)
            emit("emit $it")
        }
        .collect { println(it) }
}

8.2 Size-limiting operators

take

take 操作符只取前幾個 emit 發射的值。

fun main() = runBlocking {

    (1..5).asFlow()
        .take(2)
        .collect { println(it) }
}

8.3 Terminal flow operators

Kotlin Coroutines Flow 系列(一) Flow 基本使用 一文最後,我整理了 Flow 相關的 Terminal 操作符。本文介紹 reduce 和 fold 兩個操作符。

reduce

類似於 Kotlin 集合中的 reduce 函數,能夠對集合進行計算操作。

例如,對平方數列求和:

fun main() = runBlocking {

    val sum = (1..5).asFlow()
        .map { it * it }
        .reduce { a, b -> a + b }

    println(sum)
}

例如,計算階乘:

fun main() = runBlocking {

    val sum = (1..5).asFlow().reduce { a, b -> a * b }

    println(sum)
}

fold

也類似於 Kotlin 集合中的 fold 函數,fold 也需要設置初始值。

fun main() = runBlocking {

    val sum = (1..5).asFlow()
        .map { it * it }
        .fold(0) { a, b -> a + b }

    println(sum)
}

在上述代碼中,初始值爲0就類似於使用 reduce 函數實現對平方數列求和。

而對於計算階乘:

fun main() = runBlocking {

    val sum = (1..5).asFlow().fold(1) { a, b -> a * b }

    println(sum)
}

初始值爲1就類似於使用 reduce 函數實現計算階乘。

8.4 Composing flows operators

zip

zip 是可以將2個 flow 進行合併的操作符。

fun main() = runBlocking {

    val flowA = (1..5).asFlow()
    val flowB = flowOf("one", "two", "three","four","five")
    flowA.zip(flowB) { a, b -> "$a and $b" }
        .collect { println(it) }
}

執行結果:

1 and one
2 and two
3 and three
4 and four
5 and five

zip 操作符會把 flowA 中的一個 item 和 flowB 中對應的一個 item 進行合併。即使 flowB 中的每一個 item 都使用了 delay() 函數,在合併過程中也會等待 delay() 執行完後再進行合併。

fun main() = runBlocking {

    val flowA = (1..5).asFlow()
    val flowB = flowOf("one", "two", "three", "four", "five").onEach { delay(100) }

    val time = measureTimeMillis {
        flowA.zip(flowB) { a, b -> "$a and $b" }
            .collect { println(it) }
    }

    println("Cost $time ms")
}

執行結果:

1 and one
2 and two
3 and three
4 and four
5 and five
Cost 561 ms

如果 flowA 中 item 個數大於 flowB 中 item 個數:

fun main() = runBlocking {

    val flowA = (1..6).asFlow()
    val flowB = flowOf("one", "two", "three","four","five")
    flowA.zip(flowB) { a, b -> "$a and $b" }
        .collect { println(it) }
}

執行合併後新的 flow 的 item 個數 = 較小的 flow 的 item 個數。

執行結果:

1 and one
2 and two
3 and three
4 and four
5 and five

combine

combine 雖然也是合併,但是跟 zip 不太一樣。

使用 combine 合併時,每次從 flowA 發出新的 item ,會將其與 flowB 的最新的 item 合併。

fun main() = runBlocking {

    val flowA = (1..5).asFlow().onEach { delay(100)  }
    val flowB = flowOf("one", "two", "three","four","five").onEach { delay(200)  }
    flowA.combine(flowB) { a, b -> "$a and $b" }
        .collect { println(it) }
}

執行結果:

1 and one
2 and one
3 and one
3 and two
4 and two
5 and two
5 and three
5 and four
5 and five

flattenMerge

其實,flattenMerge 不會組合多個 flow ,而是將它們作爲單個流執行。

fun main() = runBlocking {

    val flowA = (1..5).asFlow()
    val flowB = flowOf("one", "two", "three","four","five")

    flowOf(flowA,flowB)
        .flattenConcat()
        .collect{ println(it) }
}

執行結果:

1
2
3
4
5
one
two
three
four
five

爲了能更清楚地看到 flowA、flowB 作爲單個流的執行,對他們稍作改動。

fun main() = runBlocking {

    val flowA = (1..5).asFlow().onEach { delay(100) }
    val flowB = flowOf("one", "two", "three","four","five").onEach { delay(200) }

    flowOf(flowA,flowB)
        .flattenMerge(2)
        .collect{ println(it) }
}

執行結果:

1
one
2
3
two
4
5
three
four
five

8.5 Flattening flows operators

flatMapConcat、flatMapMerge 類似於 RxJava 的 concatMap、flatMap 操作符。

flatMapConcat

flatMapConcat 由 map、flattenConcat 操作符實現。

@FlowPreview
public fun <T, R> Flow<T>.flatMapConcat(transform: suspend (value: T) -> Flow<R>): Flow<R> =
    map(transform).flattenConcat()

在調用 flatMapConcat 後,collect 函數在收集新值之前會等待 flatMapConcat 內部的 flow 完成。

fun currTime() = System.currentTimeMillis()

var start: Long = 0

fun main() = runBlocking {

    (1..5).asFlow()
        .onStart { start = currTime() }
        .onEach { delay(100) }
        .flatMapConcat {
            flow {
                emit("$it: First")
                delay(500)
                emit("$it: Second")
            }
        }
        .collect {
            println("$it at ${System.currentTimeMillis() - start} ms from start")
        }
}

執行結果:

1: First at 114 ms from start
1: Second at 619 ms from start
2: First at 719 ms from start
2: Second at 1224 ms from start
3: First at 1330 ms from start
3: Second at 1830 ms from start
4: First at 1932 ms from start
4: Second at 2433 ms from start
5: First at 2538 ms from start
5: Second at 3041 ms from start

flatMapMerge

flatMapMerge 由 map、flattenMerge 操作符實現。

@FlowPreview
public fun <T, R> Flow<T>.flatMapMerge(
    concurrency: Int = DEFAULT_CONCURRENCY,
    transform: suspend (value: T) -> Flow<R>
): Flow<R> = map(transform).flattenMerge(concurrency)

flatMapMerge 是順序調用內部代碼塊,並且並行地執行 collect 函數。

fun currTime() = System.currentTimeMillis()

var start: Long = 0

fun main() = runBlocking {

    (1..5).asFlow()
        .onStart { start = currTime() }
        .onEach { delay(100) }
        .flatMapMerge {
            flow {
                emit("$it: First")
                delay(500)
                emit("$it: Second")
            }
        }
        .collect {
            println("$it at ${System.currentTimeMillis() - start} ms from start")
        }
}

執行結果:

1: First at 116 ms from start
2: First at 216 ms from start
3: First at 319 ms from start
4: First at 422 ms from start
5: First at 525 ms from start
1: Second at 618 ms from start
2: Second at 719 ms from start
3: Second at 822 ms from start
4: Second at 924 ms from start
5: Second at 1030 ms from start

flatMapMerge 操作符有一個參數 concurrency ,它默認使用DEFAULT_CONCURRENCY,如果想更直觀地瞭解 flatMapMerge 的並行,可以對這個參數進行修改。例如改成2,就會發現不一樣的執行結果。

flatMapLatest

當發射了新值之後,上個 flow 就會被取消。

fun currTime() = System.currentTimeMillis()

var start: Long = 0

fun main() = runBlocking {

    (1..5).asFlow()
        .onStart { start = currTime() }
        .onEach { delay(100) }
        .flatMapLatest {
            flow {
                emit("$it: First")
                delay(500)
                emit("$it: Second")
            }
        }
        .collect {
            println("$it at ${System.currentTimeMillis() - start} ms from start")
        }
}

執行結果:

1: First at 114 ms from start
2: First at 220 ms from start
3: First at 321 ms from start
4: First at 422 ms from start
5: First at 524 ms from start
5: Second at 1024 ms from start

九. Flow VS Reactive Streams

天生的多平臺支持

由於 Kotlin 語言自身對多平臺的支持,使得 Flow 也可以在多平臺上使用。

互操作性

Flow 仍然屬於響應式範疇。開發者通過 kotlinx-coroutines-reactive 模塊中 Flow.asPublisher() 和 Publisher.asFlow() ,可以方便地將 Flow 跟 Reactive Streams 進行互操作。

該系列的相關文章:

Kotlin Coroutines Flow 系列(一) Flow 基本使用
Kotlin Coroutines Flow 系列(二) Flow VS RxJava2
Kotlin Coroutines Flow 系列(三) 異常處理
Kotlin Coroutines Flow 系列(四) 線程操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章