Java—Stream
什么是Stream塞颁?
Java8 中,Collection 新增了兩個流方法婶熬,分別是 Stream() 和 parallelStream()
Java8 中添加了一個新的接口類 Stream本涕,相當于高級版的 Iterator吻谋,它可以通過 Lambda 表達式對集合進行大批量數(shù)據(jù)操作,或 者各種非常便利骄噪、高效的聚合數(shù)據(jù)操作佛舱。
為什么要使用 Stream?
在 Java8 之前原杂,我們通常是通過 for 循環(huán)或者 Iterator 迭代來重新排序合并數(shù)據(jù)印颤,又或者通過重新定義 Collections.sorts 的 Comparator 方法來實現(xiàn),這兩種方式對于大數(shù)據(jù)量系統(tǒng)來說穿肄,效率并不是很理想年局。Stream 的聚合操作與數(shù)據(jù)庫 SQL 的聚合操作 sorted、filter咸产、map 等類似某宪。我們在應用層就可以高效地實現(xiàn)類似數(shù)據(jù)庫 SQL 的 聚合操作了,而在數(shù)據(jù)操作方面锐朴,Stream 不僅可以通過串行的方式實現(xiàn)數(shù)據(jù)操作兴喂,還可以通過并行的方式處理大批量數(shù)據(jù),提高數(shù)據(jù) 的處理效率。
Stream 使用入門
@Test
public void test1(){
List<String> names = Arrays.asList("張三","李四","王五","趙柳","張五六七","王少","趙四","張仁","李星");
//需求:找出 姓張中名字最長的
int maxLengthStartWithZ = names.parallelStream()
.filter(name -> name.startsWith("張"))
.mapToInt(String::length)
.max()
.getAsInt();
System.out.println(names.get(maxLengthStartWithZ));
}
Stream 操作分類
官方將 Stream 中的操作分為兩大類:終結操作(Terminal operations)和中間操作(Intermediate operations)衣迷。
中間操作會返回一個新的流畏鼓,一個流可以后面跟隨零個或多個中間操作。其目的主要是打開流壶谒,做出某種程度的數(shù)據(jù)映射/過濾云矫,
然后會返回一個新的流,交給下一個操作使用汗菜。這類操作都是惰性化的(lazy)让禀,就是說,僅僅調用到這類方法陨界,并沒有真正開始流的
遍歷巡揍。而是在終結操作開始的時候才真正開始執(zhí)行。
中間操作又可以分為無狀態(tài)(Stateless)與有狀態(tài)(Stateful)操作:
? 無狀態(tài)是指元素的處理不受之前元素的影響菌瘪;
? 有狀態(tài)是指該操作只有拿到所有元素之后才能繼續(xù)下去腮敌。
終結操作是指返回最終的結果。一個流只能有一個終結操作俏扩,當這個操作執(zhí)行后糜工,這個流就被使用“光”了,無法再被操作录淡。所以
這必定這個流的最后一個操作捌木。終結操作的執(zhí)行才會真正開始流的遍歷,并且會生成一個結果嫉戚。
終結操作又可以分為短路(Short-circuiting)與非短路(Unshort-circuiting)操作刨裆,
? 短路是指遇到某些符合條件的元素就可以得到最終結果,
? 非短路是指必須處理完所有元素才能得到最終結果彼水。操作分類詳情如下圖所示:
個別方法的測試
public class StuWithStream {
public static void main(String[] args) {
List<Student> studentList =Datainit();
groupBy(studentList);
// filter(studentList);
// total(studentList);
// MaxAndMin(studentList);
}
public static List<Student> Datainit(){
List<Student> students = Arrays.asList(
new Student("小明", 168, "男"),
new Student("大明", 182, "男"),
new Student("小白", 174, "男"),
new Student("小黑", 186, "男"),
new Student("小紅", 156, "女"),
new Student("小黃", 158, "女"),
new Student("小青", 165, "女"),
new Student("小紫", 172, "女"));
return students;
}
//Stream實現(xiàn)分組
public static void groupBy(List<Student> studentsList){
Map<String, List<Student>> groupBy = studentsList
.stream()
.collect(Collectors.groupingBy(Student::getSex));
System.out.println("分組后:"+groupBy);
}
//Stream實現(xiàn)過濾
public static void filter(List<Student> studentsList){
List<Student> filter = studentsList
.stream()
.filter(student->student.getHeight()>180)
.collect(Collectors.toList());
System.out.println("過濾后:"+filter);
}
//Stream實現(xiàn)求和
public static void total(List<Student> studentsList){
int totalHeight = studentsList
.stream()
.mapToInt(Student::getHeight)
.sum();
System.out.println(totalHeight);
}
//Stream找最大和最小
public static void MaxAndMin(List<Student> studentsList){
int maxHeight = studentsList
.stream()
.mapToInt(Student::getHeight)
.max()
.getAsInt();
System.out.println("max:"+maxHeight);
int minHeight = studentsList
.stream()
.mapToInt(Student::getHeight)
.min()
.getAsInt();
System.out.println("min:"+minHeight);
}
}
因為 Stream 操作類型非常多崔拥,總結一下常用的
- map():將流中的元素進行再次加工形成一個新流,流中的每一個元素映射為另外的元素凤覆。
- filter(): 返回結果生成新的流中只包含滿足篩選條件的數(shù)據(jù)
- limit():返回指定數(shù)量的元素的流链瓦。返回的是 Stream 里前面的 n 個元素。
- skip():和 limit()相反盯桦,將前幾個元素跳過(取出)再返回一個流慈俯,如果流中的元素小于或者等于 n,就會返回一個空的流拥峦。
- sorted():將流中的元素按照自然排序方式進行排序贴膘。
- distinct():將流中的元素去重之后輸出。
- peek():對流中每個元素執(zhí)行操作略号,并返回一個新的流刑峡,返回的流還是包含原來流中的元素洋闽。
Stream 并發(fā) Stream 源碼實現(xiàn)
并發(fā)的處理函數(shù)對比 parallelStream()方法這里的并行處理指的是,Stream 結合了 ForkJoin 框架突梦,對 Stream 處理進行了分片诫舅,Splititerator 中estimateSize 方法會估算出分片的數(shù)據(jù)量。
通過預估的數(shù)據(jù)量獲取最小處理單元的閾值宫患,如果當前分片大小大于最小處理單元的閾值刊懈,就繼續(xù)切分集合。每個分片將會生成一個
Sink 鏈表娃闲,當所有的分片操作完成后虚汛,F(xiàn)orkJoin 框架將會合并分片任何結果集。
Stream 的性能
常規(guī)數(shù)據(jù)迭代
? 100 的性能對比常規(guī)的迭代 > Stream 并行迭代> Stream 串行迭代
為什么這樣:
1皇帮、常規(guī)迭代代碼簡單卷哩,越簡單的代碼執(zhí)行效率越高。
2玲献、Stream 串行迭代殉疼,使用了復雜的設計梯浪,導致執(zhí)行速度偏低捌年。所以是性能最低的。
3挂洛、Stream 并行迭代 使用了 Fork-Join 線程池,所以效率比 Stream 串行迭代快礼预,但是對比常規(guī)迭代還是要慢(畢竟設計和代碼復雜)
大數(shù)據(jù)迭代
? 一億的數(shù)組性能對比(默認線程池)為什么這樣:
1、Stream 并行迭代 使用了 Fork-Join 線程池, 而線程池線程數(shù)為 cpu 的核心數(shù)(我的電腦為 12 核)虏劲,大數(shù)據(jù)場景下托酸,能夠利用多線
程機制,所以效率比 Stream 串行迭代快柒巫,同時多線程機制切換帶來的開銷相對來說還不算多励堡,所以對比常規(guī)迭代還是要快(雖然設計
和代碼復雜)
2、常規(guī)迭代代碼簡單堡掏,越簡單的代碼執(zhí)行效率越高应结。
3、Stream 串行迭代泉唁,使用了復雜的設計鹅龄,導致執(zhí)行速度偏低。所以是性能最低的亭畜。
? 一億的數(shù)組性能對比(線程池數(shù)量=2)為什么這樣:
Stream 并行迭代 使用了 Fork-Join 線程池,大數(shù)據(jù)場景下扮休,雖然利用多線程機制,但是線程池線程數(shù)為 2拴鸵,所以多個請求爭搶著執(zhí)行任
務玷坠,想象對請求來說任務是被交替執(zhí)行完成蜗搔,所以對比常規(guī)迭代還是要慢(雖然用到了多線程技術)
? 一億的數(shù)組性能對比(線程池數(shù)量=240)為什么這樣:
Stream 并行迭代使用了 Fork-Join 線程池, 而線程池線程數(shù)為 240,大數(shù)據(jù)場景下八堡,雖然利用多線程機制碍扔,但是線程太多,線程的上下
文切換成本過高秕重,所以導致了執(zhí)行效率反而沒有常規(guī)迭代快不同。
如何合理使用 Stream?
我們可以看到:在循環(huán)迭代次數(shù)較少的情況下溶耘,常規(guī)的迭代方式性能反而更好二拐;而在大數(shù)據(jù)循環(huán)迭代中, parallelStream(合理的線程池數(shù)上)有一定的優(yōu)勢凳兵。
但是由于所有使用并行流 parallelStream 的地方都是使用同一個 Fork-Join 線程池百新,而線程池線程數(shù)僅為 cpu 的核心數(shù)。
切記庐扫,如果對底層不太熟悉的話請不要亂用并行流 parallerStream(尤其是你的服務器核心數(shù)比較少的情況下)