Kotlin標準庫還提供了另外一種容器類型: sequences.html
Sequence
接口和Iterable
接口很像, 都是提供了遍歷操做.java
不一樣點在於, 對於集合的多步操做, Sequences提供了不一樣的作法.數組
Iterable
, 執行是急切的(eagerly), 每個步驟都完成和返回一箇中間集合(collection), 後續的操做再在這個集合上繼續執行.操做符的執行順序一樣也是不一樣的: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
變換到書名, 最後取三個書名返回.
可是須要注意的是:
這一點從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轉換, 還有用方法生成和塊生成兩種方法.
序列操做能夠分爲兩種:
大多數操做都是無狀態的, 有狀態的操做有:
若是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
仍是要根據實際用途和數據來選擇的.
影響性能的因素:
sequence優先
.take()
, contains()
, indexOf()
, any()
, none()
, find()
, first()
. -> sequence優先
.map()
操做時, 由於collection知道數量, 能夠直接設置正確的數組大小, 因此不存在消耗. 可是sequence不知道元素數量, 因此toList()
先建立一個默認大小的數組, 而後隨着元素增多, 按需擴容. -> collection優先
.collection優先
.固然終極的方法仍是實際測量一下, 才能知道到底有多大區別. 工具: JMH
除了性能, 還有其餘考慮的方面:
Kotlin的sequence和Java 8引入的stream是很像的.
主要的不一樣點是stream有parallel模式.
sequence的關鍵點:
強烈推薦這三篇文章, 裏面有個蠟筆工廠的例子很形象, 配圖很棒:
還有這個Effective Kotlin系列中sequence的這篇: