[Kotlin Tutorials 8] Sequences in Kotlin

Sequences in Kotlin

Sequences是用來幹什麼的

Kotlin標準庫還提供了另外一種容器類型: sequences.html

Sequence接口和Iterable接口很像, 都是提供了遍歷操做.java

不一樣點在於, 對於集合的多步操做, Sequences提供了不一樣的作法.數組

  • 多步處理時, 對於Iterable, 執行是急切的(eagerly), 每個步驟都完成和返回一箇中間集合(collection), 後續的操做再在這個集合上繼續執行.
  • sequences的多步處理是延遲的(lazily), 只有在整個鏈條的結果被請求的時候纔會真正執行每一步的計算.

操做符的執行順序一樣也是不一樣的:bash

  • Iterable會對集合的全部元素先完成第一個操做, 而後總體再往下走, 對全部元素進行下一個操做.
  • Sequence則按照元素, 每一個元素完成一系列操做, 接着是下一個元素.

我想了一個比喻: 雙層循環嵌套, Iterable的外層遍歷操做符, 內層遍歷元素; 而Sequence的外層遍歷元素, 內層遍歷操做符.ide

一段爲了輔助理解操做順序的僞代碼:工具

// Iterable
for (operation in operators) {
    for (element in collection) {
        //...
    }
}
// Sequence
for (element in collection) {
    for (operation in operators) {
        //...
    }
}
複製代碼

舉例說明

下面經過實際的例子來講明.性能

例子背景: 有一堆書, 書的類型是:ui

data class Book(val name: String, val author: String)
複製代碼

其中包含書名和做者名.spa

如今咱們想要在這堆書裏, 篩選出某些做者的書, 符合條件的三本就能夠了, 並輸出書名..net

用通常collection的實現:

fun findBookNamesOfAuthorsUsingCollection(authors: Set<String>): List<String> {
    return books
        .filter {
            println("filter: $it")
            it.author in authors
        }
        .map {
            println("map: $it")
            it.name
        }
        .take(3)
}
複製代碼

其中books是一個List. 上面進行的操做: 從書裏先根據做者過濾, 而後map變換到書名, 最後取三個書名返回.

可是須要注意的是:

  • 每一個操做符都會建立一個新的數組並輸出, 做爲下一級操做的輸入.
  • 若是書的數量比較多, 最後卻只取了3本, 那麼前兩步過濾和映射操做會形成浪費.

這一點從console輸出結果上就能夠看出來:

filter: Book(name=Brown Bear, Brown Bear, What Do You See?, author=Eric Carle)
filter: Book(name=1, 2, 3 to the Zoo, author=Eric Carle)
filter: Book(name=The Very Hungry Caterpillar, author=Eric Carle)
filter: Book(name=Pancakes, Pancakes!, author=Eric Carle)
filter: Book(name=The Tiny Seed, author=Eric Carle)
filter: Book(name=Do You Want to Be My Friend?, author=Eric Carle)
filter: Book(name=The Mixed-Up Chameleon, author=Eric Carle)
filter: Book(name=The Very Busy Spider, author=Eric Carle)
filter: Book(name=Papa, Please Get the Moon for Me, author=Eric Carle)
filter: Book(name=Today Is Monday, author=Eric Carle)
filter: Book(name=From Head to Toe, author=Eric Carle)
filter: Book(name=Does A Kangaroo Have A Mother, Too?, author=Eric Carle)
filter: Book(name=10 Little Rubber Ducks, author=Eric Carle)
filter: Book(name=Where Is Baby's Belly Button?, author=Karen Katz) filter: Book(name=No Biting!, author=Karen Katz) filter: Book(name=I Can Share, author=Karen Katz) filter: Book(name=My Car, author=Byron Barton) filter: Book(name=My Bus, author=Byron Barton) filter: Book(name=Dear Zoo, author=Rod Campbell) filter: Book(name=Dr. Seuss's ABC, author=Dr. Seuss)
filter: Book(name=Fox in Socks, author=Dr. Seuss)
filter: Book(name=The Cat in the Hat, author=Dr. Seuss)
filter: Book(name=Hop on Pop, author=Dr. Seuss)
map: Book(name=Brown Bear, Brown Bear, What Do You See?, author=Eric Carle)
map: Book(name=1, 2, 3 to the Zoo, author=Eric Carle)
map: Book(name=The Very Hungry Caterpillar, author=Eric Carle)
map: Book(name=Pancakes, Pancakes!, author=Eric Carle)
map: Book(name=The Tiny Seed, author=Eric Carle)
map: Book(name=Do You Want to Be My Friend?, author=Eric Carle)
map: Book(name=The Mixed-Up Chameleon, author=Eric Carle)
map: Book(name=The Very Busy Spider, author=Eric Carle)
map: Book(name=Papa, Please Get the Moon for Me, author=Eric Carle)
map: Book(name=Today Is Monday, author=Eric Carle)
map: Book(name=From Head to Toe, author=Eric Carle)
map: Book(name=Does A Kangaroo Have A Mother, Too?, author=Eric Carle)
map: Book(name=10 Little Rubber Ducks, author=Eric Carle)
map: Book(name=Where Is Baby's Belly Button?, author=Karen Katz) map: Book(name=No Biting!, author=Karen Katz) map: Book(name=I Can Share, author=Karen Katz) 複製代碼

若是換作sequence實現:

fun findBookNamesOfAuthorsUsingSequence(authors: Set<String>): List<String> {
    return books
        .asSequence()
        .filter {
            println("filter: $it")
            it.author in authors
        }
        .map {
            println("map: $it")
            it.name
        }
        .take(3)
        .toList()
}
複製代碼

相比上一段代碼, 這裏只加了asSequence()toList()兩個操做符.

看console輸出:

filter: Book(name=Brown Bear, Brown Bear, What Do You See?, author=Eric Carle)
map: Book(name=Brown Bear, Brown Bear, What Do You See?, author=Eric Carle)
filter: Book(name=1, 2, 3 to the Zoo, author=Eric Carle)
map: Book(name=1, 2, 3 to the Zoo, author=Eric Carle)
filter: Book(name=The Very Hungry Caterpillar, author=Eric Carle)
map: Book(name=The Very Hungry Caterpillar, author=Eric Carle)
複製代碼

發現找到想要的3本書以後, 對後續元素就再也不進行處理了.

並且省略了中間步驟中的集合創建.

建立和操做

建立Sequence除了常規能想到的列舉元素, 從list或set轉換, 還有用方法生成和塊生成兩種方法.

無狀態和有狀態

序列操做能夠分爲兩種:

  • 無狀態的.
  • 有狀態的.

大多數操做都是無狀態的, 有狀態的操做有:

  • 排序操做.
  • 區分操做.

intermediate和terminal操做

若是sequence的操做返回另外一個sequence, 那麼它是中間的(intermediate), 不然就是終結的(terminal), 好比 toList()sum(). 只有在這種終結操做以後, 序列的元素才能夠被獲取.

若是沒有terminal, 任何以前的intermediate操做都不會被執行.

舉例:

sequenceOf("Hello", "Kotlin", "World")
    .onEach { println(it) }
複製代碼

什麼都打印不出來.

而加上toList() (terminal操做)以後:

sequenceOf("Hello", "Kotlin", "World")
    .onEach { println(it) }
    .toList()
複製代碼

就能夠把詞都打印出來.

onEach()是中間操做, 若是想要終結操做, 用forEach(), 操做會被執行.

sequenceOf("Hello", "Kotlin", "World")
    .forEach { println(it) }
複製代碼

這是跟內部實現有關, sequence的intermediate操做符並不作實際的計算, 只是把sequence又包裝了一層. (裝飾器模式). 在terminal操做的時候, 全部的計算一塊兒進行.

內部實現的具體解釋能夠看這篇文章: Inside Sequences: Create Your Own Sequence Operations.

選擇和取捨

使用使用sequence的好處是什麼呢? 避免了中間結果的創建, 改善性能. 並且, 當咱們最後的結果是指定個數的, 取夠結果以後就不用再管後面的元素, 節省了操做.

可是在處理比較小的集合或比較簡單的操做的時候, lazy的方式也有可能帶來問題. 因此究竟用Sequence仍是Iterable仍是要根據實際用途和數據來選擇的.

影響性能的因素:

  • 操做數量. -> 操做數量越多, collection須要建立的中間集合越多. -> sequence優先.
  • 短路操做. 好比take(), contains(), indexOf(), any(), none(), find(), first(). -> sequence優先.
  • 大小可變的集合. 背景: 數組擴容. map()操做時, 由於collection知道數量, 能夠直接設置正確的數組大小, 因此不存在消耗. 可是sequence不知道元素數量, 因此toList()先建立一個默認大小的數組, 而後隨着元素增多, 按需擴容. -> collection優先.
  • 有狀態的操做, 好比排序, 中間會建立大小可變的集合, 理由同上一條. -> collection優先.

固然終極的方法仍是實際測量一下, 才能知道到底有多大區別. 工具: JMH

除了性能, 還有其餘考慮的方面:

  • 生成器. sequence沒有size, 能夠生成無限長的序列. 在請求的時候生成元素.
  • 可用的操做. sequence沒有從後到前的操做, 不支持反轉, 切片, 集合操做, 獲取單個元素的操做等.

Sequence和Java Stream

Kotlin的sequence和Java 8引入的stream是很像的.

主要的不一樣點是stream有parallel模式.

Key Takeaways

sequence的關鍵點:

  • 不建立中間集合.
  • 單元素進行全部操做, 以後再到下一個元素.
  • 中間操做符是lazy的.

參考

強烈推薦這三篇文章, 裏面有個蠟筆工廠的例子很形象, 配圖很棒:

還有這個Effective Kotlin系列中sequence的這篇:

相關文章
相關標籤/搜索