Kotlin Vocabulary | Collection 和 Sequence

在不少場景中咱們會使用到集合,Kotlin 標準庫 (Kotlin Standard Library) 中提供了很是多出色的關於集合的實用函數。其中,Kotlin 提供了基於不一樣執行方式的兩種集合類型: 當即執行 (eagerly) 的 Collection 類型, 延遲執行 (lazily) 的 Sequence 類型。本篇文章將向您介紹二者的區別,並向您介紹這兩種類型分別該在哪一種狀況下使用,以及它們的性能表現。

騰訊視頻連接:html

v.qq.com/x/page/d093…android

Bilibili 視頻連接:git

www.bilibili.com/video/av977…github

Collection 和 Sequence 的對比

當即執行和延遲執行的區別在於每次對集合進行轉換時,這個操做會在什麼時候真正執行。api

Collection(也稱集合) 是在每次操做時當即執行的,執行結果會存儲到一個新的集合中。做用於 Collection 的轉換操做是內聯函數。例如,map 的實現方式,能夠看到它是一個建立了新 ArrayList 的內聯函數:bash

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
  return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
複製代碼

Sequence (也稱序列) 是延遲執行的,它有兩種類型: 中間操做 (intermediate) 和末端操做(terminal)。中間操做不會當即執行,它們只是被存儲起來,僅當末端操做被調用時,纔會按照順序在每一個元素上執行中間操做,而後執行末端操做。中間操做 (好比 map、distinct、groupBy 等) 會返回另外一個Sequence,而末端操做 (好比 first、toList、count 等) 則不會。jvm

Sequence 是不會保留對集合項目的引用的。它基於原始集合的迭代器 (iterator) 建立,而且保留要執行的全部中間操做的引用。ide

與在 Collection 中執行轉換操做不一樣,Sequence 執行的中間轉換不是內聯函數,由於內聯函數沒法存儲,而 Sequence 須要存儲它們。咱們能夠經過下列代碼看到像 map 這樣的中間操做是如何實現的,能夠看到轉換函數會存儲在一個新的 Sequence 實例中:函數

public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R>{      
   return TransformingSequence(this, transform)
}
複製代碼

例如 first 這樣的末端操做,會對 Sequence 中的元素進行遍歷,直到預置條件匹配爲止。性能

public inline fun <T> Sequence<T>.first(predicate: (T) -> Boolean): T {
   for (element in this) if (predicate(element)) return element
   throw NoSuchElementException(「Sequence contains no element matching the predicate.」)
}
複製代碼

若是觀察 TransformingSequence 這樣的類型是如何實現的,咱們會發如今迭代器上調用 next 時,轉換存儲操做也一併被應用。

internal class TransformingIndexedSequence<T, R> 
constructor(private val sequence: Sequence<T>, private val transformer: (Int, T) -> R) : Sequence<R> {
override fun iterator(): Iterator<R> = object : Iterator<R> {
   …
   override fun next(): R {
     return transformer(checkIndexOverflow(index++), iterator.next())
   }
   …
}
複製代碼

不管您使用 Collection 仍是 Sequence,Kotlin 標準庫都提供了相似於 find、filter、groupBy 等一系列操做,在使用它們以前,您得確保瞭解這些操做

Collection 和 Sequence 如何選擇

假設咱們有一個列表,存儲了許多不一樣形狀的對象,咱們但願將列表中形狀的顏色變成黃色,而後獲取列表中的第一個正方形。

運行 Collection 和 Sequence 代碼
咱們來看一下針對 Collection 和 Sequence 的各個操做是如何執行以及什麼時候執行的。

Collections

  • 調用 map 時 —— 一個新的 ArrayList 會被建立。咱們遍歷了初始 Collection 中全部項目,複製原始的對象,而後更改它的顏色,再將其添加到新的列表中;
  • 調用 first 時 —— 遍歷每個項目,直到找到第一個正方形。

Sequences

  • asSequence —— 基於原始集合的迭代器建立一個 Sequence;
  • 調用 map 時 —— Sequence 會將轉換操做的信息存儲到一個列表中,該列表只會存儲要執行的操做,並不會執行這些操做;
  • 調用 first 時 —— 這是一個末端操做,因此會將中間操做做用到集合中的每一個元素。咱們遍歷初始集合,對每一個元素執行 map 操做,而後繼續執行 first 操做,當遍歷到第二個元素時,發現它符合咱們的要求,因此就無需在剩餘的元素中進行 map 操做了。

使用 Sequence 時不會去建立中間集合,因爲項目會被逐個執行,map 操做只會做用到部分輸入上。

Collection 和 Sequence 的對比 — 當即處理和延遲處理間的對比

性能

轉換的順序

不管您使用 Collection 仍是 Sequence,轉換的順序都很重要。在上面的例子中,first 不須要先在 map 以後進行操做,由於 first 不須要 map 操做的結果就可以執行。若是咱們顛倒業務邏輯的順序,先把 first 做用到 Collection 上,再對結果執行轉換,那麼咱們只會建立一個新的對象 —— 一個黃色的正方形。當使用 Sequence 時,會避免建立兩個新對象,而當使用 Collection 時則會避免建立整個列表。

轉換順序的重要性 — 避免無用操做
由於末端操做能夠提早對任務進行處理,而中間操做會延遲進行處理,因此在某些狀況下,相比於 Collection,Sequence 能夠避免一些無用操做。使用時,請確保檢查了轉換順序以及它們的依賴關係。

內聯和大數據集所帶來的影響

Collection 的操做使用了內聯函數,因此處理所用到的字節碼以及傳遞給它的 lambda 字節碼都會進行內聯操做。而 Sequence 不使用內聯函數,所以,它會爲每一個操做建立新的 Function 對象。

另外,Collection 會爲每一個轉換操做建立一個新的列表,而 Sequence 僅僅是保留對轉換函數的引用。

當對數據量小的 Collection 執行 1 到 2 個操做時,上面所說的差別並不會帶來什麼樣的影響,因此這種狀況下使用Collection 是沒問題的。而當列表數據很大時,中間集合的建立會很消耗資源,這種狀況下就應該使用 Sequence

不幸的是,我不知道有什麼樣的基準測試可以幫助咱們更好地探索出具體不一樣大小的集合或者操做鏈纔會對 Collection 和 Sequence 產生影響。

綜上所述,Collection 會當即執行對數據的操做,而 Sequence 則是延遲執行。根據要處理的數據量大小,選擇最合適的一個: 數據量小,則使用 Collection,數據量大,則使用 Sequence,另外,需注意操做順序帶來的影響。

點擊這裏瞭解更多關於用 Kotlin 進行 Android 開發的相關資料

相關文章
相關標籤/搜索