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緩存
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