Kotlin 性能優化利器 —— Sqeuence 原理淺析

前言

本文將介紹 Kotlin 中 序列(Sequence)的概念及使用,並介紹該惰性集合操做對集合鏈式調用性能優化背後的原理。java

閱讀本文大概須要 5 分鐘,寫做本文大概消耗 7 個小時性能優化

目錄

目錄

序列(Sequence)

概念

在使用 Kotlin 集合操做符進行鏈式調用時,例如 mapfilter 時,都會在函數內部建立中間集合,好比下面的例子,使用 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 是成員引用,具體介紹以下性能

成員引用(Member References)

概念

成員引用可使你方便的調用某個類的成員,這個成員包括對應類的屬性或方法.雙冒號前的是被引用的類,雙冒號後是須要返回的屬性或方法名,以下所示是返回 User 成員的 sex 屬性:測試

User :: sex
複製代碼

成員引用能夠方便的賦值給其餘變量或函數,例如上述尋找性別爲 male 的例子,也能夠用稍微複雜的寫法,以下:優化

users.map(user : User -> user.sex)
     .filter {it.sex.equals("male")}
複製代碼

可見成員引用的寫法可讀性更強。spa

再談序列

讓咱們回到序列介紹。上文提到使用 mapfilter 時,都會在函數內部建立中間集合,這會致使一個問題,若是源列表,就是 users 中元素特別多,集合的鏈式處理會變得十分低效,緣由是建立了屢次中間集合。而若是先將待處理集合經過 asSequence() 方法轉換爲序列,再進行 mapfilter 操做,就會變得十分高效。對因而否使用序列進行集合操做,有幾個前提,若是使用不當,反而會形成性能損失。這裏總結一下使用場景:.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")

}
複製代碼

測試結果折線圖以下,其中橫座標爲集合內元素的個數,縱座標爲代碼執行時間,橙色線表明未使用序列,藍色線表明使用序列:

消耗時間折線對比圖
表格對好比下:

表格對比

由圖可得出以下結論:

  • 上文提到的閾值大體爲「一百萬」個元素,大於該閾值時,使用序列大體能帶來 90 % 的性能提高
  • 在小於「十萬」個元素時,使用序列反而會形成性能降低

爲何序列會提升集合操做的性能?

  1. 序列對集合的操做是惰性的。
  2. 不須要額外的建立中間集合保存鏈式操做的中間結果

對於第一點,惰性這個詞可能給人帶來迷惑,這和使用序列後,集合的計算方式有關,下面來介紹這一點,在下面這個例子中,你須要在一個整型數據集合,將每一個數乘以 2 ,並找出集合中小於 10 的第一個元素。

var result = listOf(2,4,6,8,10).asSequence
                  .map(it * 2)
                  .find(it > 10)
複製代碼

find() 方法的做用是在集合中查找知足條件的第一個元素,並返回查找到的值。下圖是 Kotlin 使用序列進行處理

序列使用對比圖

由圖可知,所謂惰性,就是在使用 mapfilter 等操做符的時候,在代碼的執行順序上,不會優先遍歷全部的集合,即偷個懶,先對集合中第一個元素進行 map 和 filter, 而後對該元素執行 find 操做,發現知足 find 的條件,就馬上返回結果,因而可知,在有 map 和 find 或 last 等操做符組合時,序列的性能優化能發生最大的做用。

小結

  1. 在進行集合操做時,使用序列操做符,能夠下降集合操做的時間和佔用的空間,下降時間的緣由是惰性操做,下降空間佔用的緣由是序列在執行操做時不會建立中間集合。
  2. 序列操做雖好,但也要視業務場景決定是否使用,不然反而會下降效率。

參考文獻

您的點贊或評論是對我寫做最大的鼓勵 !

相關文章
相關標籤/搜索