Java8 Stream流使用及其基本原理


Stream流,是對集合對象操作的增強

基本使用

比如有一個Person類的集合:List<Person> personList,可以通過stream()對集合中的元素進行操作,

下面的操作流程可以歸納為 過濾-映射-收集。

List<Integer> personIdList = personList.stream()
     //選出年齡大於20的Person對象
.filter(person -> person.getAge() > 20)
//將Person對象映射成為它的屬性id
     .map(Person::getId)
     //收集為List集合
.collect(Collectors.toList());

 上述代碼獲取到了,年齡大於20歲的人id集合。

在 過濾-映射-收集 這個流程中:

     過濾和映射屬於中間操作,當操作結束時才會觸發計算,可以高效地迭代大集合

  收集屬於結束操作,觸發計算。

中間操作和結束操作

流操作可以分為 中間操作(惰性求值) 和 結束操作

中間操作指,操作過程中只記錄操作而不做執行,直到執行結束操作,才會觸發實際的操作,即惰性求值。

  中間操作又分為:

    無狀態操作,元素處理不受之前元素影響,比如map()、filter()、skip()、peek()等

    有狀態操作,需要拿到所有元素才能進行,比如sorted()、limit()、distinct()等

 

 結束操作是指,拿到所有元素后才能進行的操作。

  結束操作又分為:

    短路操作,遇到符合條件的元素后就可以終止,比如anyMatch()、findFirst()、findAny()等

    非短路操作,需要處理所有元素,比如forEach()、collect()、max()、count()等。

 

例子

1.將List<Person>映射成一個Map,key為Person的id屬性,value為Person實體類

Map<Integer, Person> PersonMap= personList.stream()
.collect(Collectors.toMap(Person::getId, person -> person));

2.得到一個Map<Integer,List<Person>>集合,key為Person的age屬性,value是按年齡分組后的Person對象集合:

Map<Integer, List<Person>> personsMap = personList.stream()
.collect(Collectors.groupingBy(Person::getAge));

3.統計所有人年齡的總和:

int personAgeSum = personList.stream()
     //根據age屬性轉換成IntStream
.mapToInt(Person::getAge)
.sum();

4.選出List集合中創建時間最晚的數據(createtime屬性為Date類型)

UserInfo userInfoMax = userInfos.stream()
     .max(Comparator.comparing(UserInfo::getCreateTime))
.get();  


這里的max方法實際返回的是Optional<UserInfo>對象該對象可以通過orElse()方法設置對象UserInfo為null時的值:
UserInfo userInfoMax = userInfos.stream().max(Comparator.comparing(UserInfo::getCreateTime))
.orElse(為null時的值); //這里如果max返回的對象為null(一般情況不會是),會取orElse()中的值

5.將所有小寫字母拼接起來

String concat = Stream.of("a", "B", "c", "D", "e", "F")
                .filter(x -> x.compareTo("Z") > 0) .reduce("", String::concat);

reduce方法的第一個參數是起始值,第二個參數是流中的元素,迭代流中的數據

也可以只傳一個參數,即不指定起始值,這樣會返回一個Optional對象

String concat = Stream.of("a", "B", "c", "D", "e", "F") .filter(x -> x.compareTo("Z") > 0) .reduce(String::concat) .orElse("");

6.迭代map

使用foreach迭代map:albumsByArtist是一個map集合

Map<Artist, Integer> countOfAlbums = new HashMap<>(); 
albumsByArtist.forEach((artist, albums) -> { countOfAlbums.put(artist, albums.size()); });

 

7.使用map緩存

computeIfAbsent()方法接受一個 Lambda 表達式,當值不存在時使用 Lambda 表達式計算新值

Map<String, Artist> artistCache = new HashMap<>();
根據key值name從artistCache獲取對應的value,如果沒有則調用readArtistFromDB方法獲取值
public Artist getArtist(String name) {
  return artistCache.computeIfAbsent(name, this::readArtistFromDB); 
}

 

 

性能及基本原理簡述

Stream 顧名思義,相當於批量處理數據的流水線。

在處理一般的數據量下,使用循環方式處理集合和通過stream方式處理集合的性能相差不大,但在數據里更大邏輯更復雜的情況下stream要更優。

而parallelStream並行流,利用多核處理器的優勢,並行(同一時間發生)處理數據(這意味着所處理的數據,不應該和順序相關,否則會因為並行得到錯誤的結果),能夠顯著的提高執行速度。

Java8函數式編程書中提到,影響並行流的因素包括:

1.數據大小,並行化處理會帶來額外的開銷,只有每個數據處理管道花費的時間足夠多時才有意義

2.數據結構,分割數據源,即把原有集合分成若干個集合到管道中

3.裝箱,處理基本類型要比處理裝箱類型快,裝箱類型可以使用mapToInt等方法

4.處理器核的數量

5.單元處理開銷:處理單個元素的花費時間越長,並行操作帶來的性能提升越明顯

 

 

Stream處理過程簡述:

首先將Collection轉化為Stream,流上的每個結點都只會返回包含上一結點引用的中間結點,使結點們組成了一個雙向鏈表結構,而不會立即進行任何數據處理。

每個中間操作都實現了Sink接口,實現了 makeSink() 方法用來返回自己的 Sink 實例,

只有當終止操作出現時,才開始將 Sink 實例化執行數據處理,

無狀態操作的 Sink 接收到數據,會立即通知下游,有狀態操作的 Sink 則會等要處理的數據處理完了才開始通知下游,

終止節點將數據收集起來后就完成了這次操作。

 

參考文章:

https://www.cnblogs.com/CarpenterLee/archive/2017/03/28/6637118.html

https://zhuanlan.zhihu.com/p/52579165

https://zhuanlan.zhihu.com/p/47478339


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM