本文將介紹 Kotlin 中 序列(Sequence)的概念及使用,並介紹該惰性集合操做對集合鏈式調用性能優化背後的原理。java
閱讀本文大概須要 5 分鐘,寫做本文大概消耗 7 個小時性能優化
在使用 Kotlin 集合操做符進行鏈式調用時,例如 map
和 filter
時,都會在函數內部建立中間集合,好比下面的例子,使用 map 和 filter 在 User 集合中篩選出性別爲男的成員,返回結果是一個集合。bash
users.map(User :: sex)
.filter {it.sex.equals("male")}
複製代碼
序列的用法很簡單,只須要再集合後添加asSeqence()
函數便可函數
users.asSequence()
.map(User :: sex)
.filter {it.sex.equals("male")}
複製代碼
這裏插播一個概念,其中 User :: user
是成員引用,具體介紹以下性能
成員引用可使你方便的調用某個類的成員,這個成員包括對應類的屬性或方法.雙冒號前的是被引用的類,雙冒號後是須要返回的屬性或方法名,以下所示是返回 User 成員的 sex 屬性:測試
User :: sex
複製代碼
成員引用能夠方便的賦值給其餘變量或函數,例如上述尋找性別爲 male 的例子,也能夠用稍微複雜的寫法,以下:優化
users.map(user : User -> user.sex)
.filter {it.sex.equals("male")}
複製代碼
可見成員引用的寫法可讀性更強。spa
讓咱們回到序列介紹。上文提到使用 map
和 filter
時,都會在函數內部建立中間集合,這會致使一個問題,若是源列表,就是 users 中元素特別多,集合的鏈式處理會變得十分低效,緣由是建立了屢次中間集合。而若是先將待處理集合經過 asSequence()
方法轉換爲序列,再進行 map
和 filter
操做,就會變得十分高效。對因而否使用序列進行集合操做,有幾個前提,若是使用不當,反而會形成性能損失。這裏總結一下使用場景:.net
上文提到,是否使用序列的條件之一是處理大量數據,那麼這個閾值到底是多少?下面來進行一個性能測試,構造一個商品列表,其中每一個商品包含商品名和價格兩個屬性,如今要求出對每一個商品的價格加價 100 後,價格爲奇數 的商品的個數,這裏用到了 count()
方法,做用是求得集合內知足 count 條件的元素的個數,代碼以下:code
/** * 商品類 */
data class Commodity(var name: String, var price: String)
複製代碼
import java.util.*
fun main(args: Array<String>) {
val commodityList = ArrayList<Commodity>()
for (i in 0..1000000) {
val goods = Commodity("商品 $i", i * 5)
commodityList.add(goods)
}
val startTime = System.currentTimeMillis()
commodityList
.asSequence() // 使用此函數表明使用 Kotlin 序列功能
.map { it.price + 100 }
.count { it % 2 != 0 }
println("consume time is ${System.currentTimeMillis() - startTime} ms")
}
複製代碼
測試結果折線圖以下,其中橫座標爲集合內元素的個數,縱座標爲代碼執行時間,橙色線表明未使用序列,藍色線表明使用序列:
表格對好比下:由圖可得出以下結論:
對於第一點,惰性這個詞可能給人帶來迷惑,這和使用序列後,集合的計算方式有關,下面來介紹這一點,在下面這個例子中,你須要在一個整型數據集合,將每一個數乘以 2 ,並找出集合中小於 10 的第一個元素。
var result = listOf(2,4,6,8,10).asSequence
.map(it * 2)
.find(it > 10)
複製代碼
find()
方法的做用是在集合中查找知足條件的第一個元素,並返回查找到的值。下圖是 Kotlin 使用序列進行處理
由圖可知,所謂惰性,就是在使用 map
或 filter
等操做符的時候,在代碼的執行順序上,不會優先遍歷全部的集合,即偷個懶,先對集合中第一個元素進行 map 和 filter, 而後對該元素執行 find 操做,發現知足 find 的條件,就馬上返回結果,因而可知,在有 map 和 find 或 last 等操做符組合時,序列的性能優化能發生最大的做用。