JDK8 Stream 數據流效率分析

JDK8 Stream 數據流效率分析

 

Stream 是Java SE 8類庫中新增的關鍵抽象,它被定義於 java.util.stream (這個包裏有若干流類型: Stream<T> 表明對象引用流,此外還有一系列特化流,如 IntStream,LongStream,DoubleStream等 ),Java 8 引入的的Stream主要用於取代部分Collection的操做,每一個流表明一個值序列,流提供一系列經常使用的彙集操做,能夠便捷的在它上面進行各類運算。集合類庫也提供了便捷的方式使咱們能夠以操做流的方式使用集合、數組以及其它數據結構;html

 

stream 的操做種類

①中間操做 java

- 當數據源中的數據上了流水線後,這個過程對數據進行的全部操做都稱爲「中間操做」;
- 中間操做仍然會返回一個流對象,所以多箇中間操做能夠串連起來造成一個流水線;
- stream 提供了多種類型的中間操做,如 filter、distinct、map、sorted 等等;算法

②終端操做 數組

- 當全部的中間操做完成後,若要將數據從流水線上拿下來,則須要執行終端操做;數據結構

- stream 對於終端操做,能夠直接提供一箇中間操做的結果,或者將結果轉換爲特定的 collection、array、String 等;app

這一部分詳細的說明能夠參見:JDK8 Stream 詳細使用函數

 

stream 的特色

①只能遍歷一次:oop

數據流的從一頭獲取數據源,在流水線上依次對元素進行操做,當元素經過流水線,便沒法再對其進行操做,能夠從新在數據源獲取一個新的數據流進行操做;性能

②採用內部迭代的方式:測試

對Collection進行處理,通常會使用 Iterator 遍歷器的遍歷方式,這是一種外部迭代;

而對於處理Stream,只要申明處理方式,處理過程由流對象自行完成,這是一種內部迭代,對於大量數據的迭代處理中,內部迭代比外部迭代要更加高效;

 

stream 相對於 Collection 的優勢

  • 無存儲:流並不存儲值;流的元素源自數據源(多是某個數據結構、生成函數或I/O通道等等),經過一系列計算步驟獲得;
  • 函數式風格:對流的操做會產生一個結果,但流的數據源不會被修改;
  • 惰性求值:多數流操做(包括過濾、映射、排序以及去重)均可以以惰性方式實現。這使得咱們能夠用一遍遍歷完成整個流水線操做,並能夠用短路操做提供更高效的實現;
  • 無需上界:很多問題均可以被表達爲無限流(infinite stream):用戶不停地讀取流直到滿意的結果出現爲止(好比說,枚舉 完美數 這個操做能夠被表達爲在全部整數上進行過濾);集合是有限的,但流能夠表達爲無線流;
  • 代碼簡練:對於一些collection的迭代處理操做,使用 stream 編寫能夠十分簡潔,若是使用傳統的 collection 迭代操做,代碼可能十分囉嗦,可讀性也會比較糟糕;

 


stream 和 iterator 迭代的效率比較

好了,上面 stream 的優勢吹了那麼多,stream 函數式的寫法是很舒服,那麼 steam 的效率到底怎樣呢?

先說結論:

- 傳統 iterator (for-loop) 比 stream(JDK8) 迭代性能要高,尤爲在小數據量的狀況下;

- 在多核情景下,對於大數據量的處理,parallel stream 能夠有比 iterator 更高的迭代處理效率;


我分別對一個隨機數列 List (數量從 10 到 10000000)進行映射、過濾、排序、規約統計、字符串轉化場景下,對使用 stream 和 iterator 實現的運行效率進行了統計,測試代碼 基準測試代碼連接

測試環境以下:

System:Ubuntu 16.04 xenial

CPU:Intel Core i7-8550U

RAM:16GB

JDK version:1.8.0_151

JVM:HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

JVM Settings:

    -Xms1024m

    -Xmx6144m

    -XX:MaxMetaspaceSize=512m

    -XX:ReservedCodeCacheSize=1024m

    -XX:+UseConcMarkSweepGC

    -XX:SoftRefLRUPolicyMSPerMB=100

 

1. 映射處理測試

把一個隨機數列(List<Integer>)中的每個元素自增1後,從新組裝爲一個新的 List<Integer>,測試的隨機數列容量從 10 - 10000000,跑10次取平均時間;

  
  
  
  
  
  1. //stream
  2. List<Integer> result = list.stream()
  3. .mapToInt(x -> x)
  4. .map(x -> ++x)
  5. .boxed()
  6. .collect(Collectors.toCollection(ArrayList:: new));
  7. //iterator
  8. List<Integer> result = new ArrayList<>();
  9. for(Integer e : list){
  10. result.add(++e);
  11. }
  12. //parallel stream
  13. List<Integer> result = list.parallelStream()
  14. .mapToInt(x -> x)
  15. .map(x -> ++x)
  16. .boxed()
  17. .collect(Collectors.toCollection(ArrayList:: new));

2. 過濾處理測試

取出一個隨機數列(List<Integer>)中的大於 200 的元素,並組裝爲一個新的 List<Integer>,測試的隨機數列容量從 10 - 10000000,跑10次取平均時間;

  
  
  
  
  
  1. //stream
  2. List<Integer> result = list.stream()
  3. .mapToInt(x -> x)
  4. .filter(x -> x > 200)
  5. .boxed()
  6. .collect(Collectors.toCollection(ArrayList:: new));
  7. //iterator
  8. List<Integer> result = new ArrayList<>(list.size());
  9. for(Integer e : list){
  10. if(e > 200){
  11. result.add(e);
  12. }
  13. }
  14. //parallel stream
  15. List<Integer> result = list.parallelStream()
  16. .mapToInt(x -> x)
  17. .filter(x -> x > 200)
  18. .boxed()
  19. .collect(Collectors.toCollection(ArrayList:: new));

3. 天然排序測試

對一個隨機數列(List<Integer>)進行天然排序,並組裝爲一個新的 List<Integer>,iterator 使用的是 Collections # sort API(使用歸併排序算法實現),測試的隨機數列容量從 10 - 10000000,跑10次取平均時間;

  
  
  
  
  
  1. //stream
  2. List<Integer> result = list.stream()
  3. .mapToInt(x->x)
  4. .sorted()
  5. .boxed()
  6. .collect(Collectors.toCollection(ArrayList:: new));
  7. //iterator
  8. List<Integer> result = new ArrayList<>(list);
  9. Collections.sort(result);
  10. //parallel stream
  11. List<Integer> result = list.parallelStream()
  12. .mapToInt(x->x)
  13. .sorted()
  14. .boxed()
  15. .collect(Collectors.toCollection(ArrayList:: new));

4. 歸約統計測試

獲取一個隨機數列(List<Integer>)的最大值,測試的隨機數列容量從 10 - 10000000,跑10次取平均時間;

  
  
  
  
  
  1. //stream
  2. int max = list.stream()
  3. .mapToInt(x -> x)
  4. .max()
  5. .getAsInt();
  6. //iterator
  7. int max = - 1;
  8. for(Integer e : list){
  9. if(e > max){
  10. max = e;
  11. }
  12. }
  13. //parallel stream
  14. int max = list.parallelStream()
  15. .mapToInt(x -> x)
  16. .max()
  17. .getAsInt();

5. 字符串拼接測試

獲取一個隨機數列(List<Integer>)各個元素使用「,」分隔的字符串,測試的隨機數列容量從 10 - 10000000,跑10次取平均時間;

  
  
  
  
  
  1. //stream
  2. String result = list.stream().map(String::valueOf).collect(Collectors.joining( ","));
  3. //iterator
  4. StringBuilder builder = new StringBuilder();
  5. for(Integer e : list){
  6. builder.append(e).append( ",");
  7. }
  8. String result = builder.length() == 0 ? "" : builder.substring( 0,builder.length() - 1);
  9. //parallel stream
  10. String result = list.stream().map(String::valueOf).collect(Collectors.joining( ","));

6. 混合操做測試

對一個隨機數列(List<Integer>)進行去空值,除重,映射,過濾,並組裝爲一個新的 List<Integer>,測試的隨機數列容量從 10 - 10000000,跑10次取平均時間;

  
  
  
  
  
  1. //stream
  2. List<Integer> result = list.stream()
  3. .filter(Objects::nonNull)
  4. .mapToInt(x -> x + 1)
  5. .filter(x -> x > 200)
  6. .distinct()
  7. .boxed()
  8. .collect(Collectors.toCollection(ArrayList:: new));
  9. //iterator
  10. HashSet<Integer> set = new HashSet<>(list.size());
  11. for(Integer e : list){
  12. if(e != null && e > 200){
  13. set.add(e + 1);
  14. }
  15. }
  16. List<Integer> result = new ArrayList<>(set);
  17. //parallel stream
  18. List<Integer> result = list.parallelStream()
  19. .filter(Objects::nonNull)
  20. .mapToInt(x -> x + 1)
  21. .filter(x -> x > 200)
  22. .distinct()
  23. .boxed()
  24. .collect(Collectors.toCollection(ArrayList:: new));

 

實驗結果總結

從以上的實驗來看,能夠總結處如下幾點:

- 在少低數據量的處理場景中(size<=1000),stream 的處理效率是不如傳統的 iterator 外部迭代器處理速度快的,可是實際上這些處理任務自己運行時間都低於毫秒,這點效率的差距對普通業務幾乎沒有影響,反而 stream 可使得代碼更加簡潔;

- 在大數據量(szie>10000)時,stream 的處理效率會高於 iterator,特別是使用了並行流,在cpu剛好將線程分配到多個核心的條件下(固然parallel stream 底層使用的是 JVM 的 ForkJoinPool,這東西分配線程自己就很玄學),能夠達到一個很高的運行效率,然而實際普通業務通常不會有須要迭代高於10000次的計算;

- Parallel Stream 受引 CPU 環境影響很大,當沒分配到多個cpu核心時,加上引用 forkJoinPool 的開銷,運行效率可能還不如普通的 Stream;

 

使用 Stream 的建議

- 簡單的迭代邏輯,能夠直接使用 iterator,對於有多步處理的迭代邏輯,可使用 stream,損失一點幾乎沒有的效率,換來代碼的高可讀性是值得的

- 單核 cpu 環境,不推薦使用 parallel stream,在多核 cpu 且有大數據量的條件下,推薦使用 paralle stream;

- stream 中含有裝箱類型,在進行中間操做以前,最好轉成對應的數值流,減小因爲頻繁的拆箱、裝箱形成的性能損失;

 

原文地址:https://blog.csdn.net/Al_assad/article/details/82356606

相關文章
相關標籤/搜索