在上一章裡我們提到 Sequence<T>
這個跟 Iterable<T>
用起來很像,但實際行為有點不同的物件。我們可以把 Iterable<T>
想像成是急驚風個性,只要一被呼叫就會馬上把所有資料處理完,也因此中間會產生很多過渡用的 Collection;相對 Sequence<T>
就是位慢郎中,只有在真正要取資料時才會開始動作,而且花的步驟愈少愈好。
在知道這兩種物件的特性後,應該會覺得 Sequence<T>
是個效能比較好的版本?但效能這種事其實沒這麼絕對,所以大家一定會很好奇,到底什麼時候該用哪一個?
在這個章節裡,我們依照 typealias.com 給出的建議整理給大家參考:
還記得在上一章的說明裡我們提到,Iterable
在執行像 filter()
、map()
這種操作時,會產生暫時性的 Collection。而當這種暫時性的 Collection 愈多時,也會愈消耗資源。因此,假如你對要資料做很多次的操作的話,那 Sequence
會有比較好的效能。
Sequence<T>
和 Iterable<T>
在處理元素時的順序是不同的,還記得上一章的流程圖裡,Iterable<T>
會把 Collection 裡的所有元素都碰過一遍才執行下一個動作,而 Sequence<T>
則是一次拿一個元素出來,把所有動作執行後,才處理下一個元素。因此,假如取回的元素數量是小量的,那 Sequence<T>
會有比較好的效能。
有許多操作都限制取回元素的數量,像是 take()
可以指定取出的數量;contains()
及 indexOf()
只要定址到元素的位置就不需要再往下找;any()
、none()
及 find()
只要找到對應的元素後就不需要再往下找;first()
只需要取出第一個,後面的通通都可以跳過。假如 Collection 操作的最後一個動作是以上這些的話,那就改用 Sequence<T>
吧!
雖然前面兩個因子都是 Sequence<T>
的效能比較好,但假如處理完資料後要再轉回 Collection 的話,Sequence<T>
的表現就沒這麼理想了。原因是 Iterable<T>
從頭到尾都知道自己的元素有多少個,因此在操作在 ArrayList
的底層時,可以直接分配正確尺寸;反觀 Sequence<T>
因為不知道元素有幾個,所以在取回資料時,要不斷調整 ArrayList 的大小,中間會有複製、分配等大量的底層操作,因此在toList()
或 toSet()
時,效能較差。
所以今天若是不需要考量取回結果的操作像 forEach()
,Sequence<T>
的效能比較好;但若需取回的類別是 List
或 Set
的話,則用 Iterable<T>
就可以了。
Sequence<T>
支援的操作大多都是 Stateless 的,而當操作是需要 State 時,Sequence 底層的實作會暫時把 Sequence 轉成 Collection,這過程就會有一些效能的損耗,像是 sortedBy()
或 distinct()
就是這一類的操作。所以,當今天都是 Stateless 的操作像 map()
、filter()
時,Sequence<T>
有比較好的效能,但若是 Stateful 操作的話,Iterable<T>
有比較好的效能。
雖然我們都希望程式的效能很好,但開發不是只考慮效能,開發時的方便性及後續的維護也很重要。Sequence<T>
雖然看似是個在許多情境下有較好效能的選項,但相對來說,這個物件身上有的屬性和方法也比較少。比方說 Sequence 因為沒有大小的概念,因此身上不會有 size
或 isEmpty()
等方法。甚至 Sequence 因為可以無限大,所以也無法反向取出資料,所以像 Right
、Last
結尾的 method 都不存在,也無法 slice()
、reverse()
。甚至許多理論操作像 union()
、intersect()
或是 random()
都沒辦法使用。因此,假如你的資料操作需要這些功能的話,繼續用 Iterable<T>
吧!
在這個章節裡,我們討論了幾個會影響 Sequence<T>
及 Iterable<T>
的因子,了解這些因素可以幫助我們決定何時該使用哪一種物件。不過要注意的是,這些因子也會彼此影響,所以當你的操作有複合因子的時候,到底該用哪個效能比較好,有時也很難說個準。因此在實際開發時,若是對效能有強烈要求,建議拿真實資料來跑 Benchmark 會比較準確。