[譯]Kotlin中是應該使用序列(Sequences)還是集合(Lists)?

極客熊貓發表於2018-06-03

翻譯說明:

原標題: Sequences — a Pragmatic Approach

原文地址: proandroiddev.com/sequences-a…

原文作者: Tomek Polański

序列(Sequences) 是一個很棒的工具,它有一些不同於Android開發人員習慣的處理資料集合的方法。在我之前的文章中,我比較了各種操作集合的方式,現在我想給你介紹關於什麼時候使用Sequences(序列),什麼時候該使用Lists(標準集合)

什麼時候使用Sequences(序列)

連結多個操作符

處理集合時效能損耗的最大原因是迴圈。集合元素迭代的次數越少效能越好。我們舉個例子:

list
  .map { it + 1 }
  .filter { it % 2 == 0 }
  .count { it < 10 } //286 μs
複製程式碼

decompile code

Collection<Integer> destination = new ArrayList<>(list.size());
Iterable<Integer> iterable = list;
Iterator<Integer> iterator = iterable.iterator();

while (iterator.hasNext()) {
    int it = iterator.next();
    destination.add(it + 1);
}

iterable = destination;
destination = new ArrayList<>();
iterator = iterable.iterator();

while (iterator.hasNext()) {
    int it = iterator.next();
    if (it % 2 == 0) {
        destination.add(it);
    }
}

iterable = destination;
int count = 0;
iterator = iterable.iterator();

while (iterator.hasNext()) {
     int it = iterator.next();
    if (it < 10) {
        ++count;
    }
}
return count;
複製程式碼

當你反編譯上述程式碼的時候,你會發現Kotlin編譯器會建立三個while迴圈.其實你可以使用指令式程式設計方式利用一個迴圈就能實現上面相同任務的需求。不幸的是,編譯器無法將程式碼優化到這樣的程度。

序列(Sequences) 的祕訣在於它們是共享同一個迭代器(iterator) ---序列允許 map操作 轉換一個元素後,然後立馬可以將這個元素傳遞給 filter操作 ,而不是像集合(lists) 一樣等待所有的元素都迴圈完成了map操作後,用一個新的集合儲存起來,然後又遍歷迴圈從新的集合取出元素完成filter操作。通過減少迴圈次數,該序列為我們提供了26%(List為286μs,Sequence為212μs)效能提升:

list
  .asSequence()
  .map { it + 1 }
  .filter { it % 2 == 0 }
  .count { it < 10 } //212 μs
複製程式碼

使用first{...}或者last{...}操作符

當使用接收一個預判斷的條件 first or last 方法時候,使用**序列(Sequences)**會產生一個小的效能提升,如果將它與其他操作符結合使用,它的效能將會得到更大的提升。

list
  .map { it + 1 }
  .first { it % 100 == 0 } // 232 μs
複製程式碼

使用了序列(Sequences) 的版本:

list
  .asSequence()
  .map { it + 1 }
  .first { it % 100 == 0 } // 8 μs
複製程式碼

通過對比我們可以看到有了97%的效能提升。

什麼時候使用Lists(集合)

量級比較小的集合元素

Kotlin Lists API在處理一些小量級的集合元素(比如說少於100個)時仍然非常有效,你不應該在乎它是否需要0.000007s(7μs)或0.000014s(14μs),通常不值得花了很大功夫去進行優化。

訪問索引元素

Lists(集合) 是有索引的,這就是為什麼按索引訪問專案非常快並且具有恆定時間複雜度的原因。在另一方面,Sequences(序列) 則必須逐項進行,直到它們到達目標專案。

請注意,對於不需要滿足預判斷條件的first() 或者 last()方法,它們在內部則是使用index(索引)來訪問List中的元素--這就是為什麼它們相對於Sequences會更快。

返回/傳遞給其他的函式

每次迭代Sequences(序列) 時,都會計算元素。Lists(集合) 中的元素只計算一次,然後儲存在記憶體中。

這就是為什麼你不應該將Sequences(序列) 作為引數傳遞給函式: 函式可能會多次遍歷它們。在傳遞或者在整個使用List之前建議將Sequences(序列) 轉換 Lists(集合)

如果你真的想要傳遞一個Sequences(序列),你可以使用constrainOnce() - 它只允許一次遍歷Sequences(序列),第二次嘗試遍歷會丟擲一個異常。 不過,我不會建議這種方法,因為它使程式碼難以維護。

您也可以使用這個簡單的決策樹來決定選擇一個Sequences(序列) 還是一個 Lists(集合)

[譯]Kotlin中是應該使用序列(Sequences)還是集合(Lists)?

如果您的應用程式處理大量資料,Sequences(序列) 將為您帶來不錯的效能提升。不過,您不需要在程式碼中更改所有用到List的地方,而是真正需要去查明影響效能的瓶頸,然後去解決它。

譯者有話說

  • 1、為什麼我要翻譯這篇部落格?

序列(Sequences) 可以說是優化集合中一些操作效能的工具,它實際上和Java8中的Stream功能類似,可能有時候我們一些初學者還不能夠很好的去駕馭它。不知道什麼時候該用序列(Sequences) 什麼時候該用 集合(Lists),可能很多人很難發覺他們有什麼不同,因為我們平時操作的資料集合量級很小,效能損耗差不多,但是一旦處於比較大資料量級,它們之間的差異將會非常明顯。然而這篇部落格的原作者做過一個這樣對比,比較了序列(Sequences)、集合(Lists)、RxJava三者之間在同一個資料量級的效能對比。作者列出詳細圖表對比(詳細可見這篇部落格: Declarative Kotlin: Lists, Sequences and RxJava. 所以學完在合適的時機,選擇正確運算元據集合方式非常重要,所以這是我翻譯這篇部落格初衷。

  • 2、關於什麼使用序列(Sequences) 提煉幾點。

第一、資料集量級是足夠大,建議使用序列(Sequences)

第二、對資料集進行頻繁的資料操作,類似於多個操作符鏈式操作,建議使用序列(Sequences)

第三、對於使用first{},last{}建議使用序列(Sequences)。補充一下,細心的小夥伴會發現當你對一個集合使用first{},last{}操作符的時候,我們IDE工具會提示你建議使用序列(Sequences) 代替 集合(Lists),這時候足以看出Kotlin這門語言在IDE支援方面,有得天獨厚的優勢,畢竟人家Kotlin是JetBrains公司的親兒子。

  • 3、總結

關於序列(Sequences) 實際上這篇部落格只是大概給出了使用序列時機,但是序列在底層實現上為什麼效能會優於集合,以及序列更多細節的內容只是一筆帶過,那麼我的下篇部落格將會深入解析Kotlin中的序列,而這篇部落格算是有個大概的認識。

[譯]Kotlin中是應該使用序列(Sequences)還是集合(Lists)?

歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~

相關文章