Java 8 Stream 數(shù)據(jù)流效率分析
來源:https://blog.csdn.net/Al_assad/article/details/82356606
Stream 是Java SE 8類庫中新增的關(guān)鍵抽象,它被定義于 java.util.stream (這個包里有若干流類型:Stream
stream 的操作種類
① 中間操作
當(dāng)數(shù)據(jù)源中的數(shù)據(jù)上了流水線后,這個過程對數(shù)據(jù)進(jìn)行的所有操作都稱為“中間操作”; 中間操作仍然會返回一個流對象,因此多個中間操作可以串連起來形成一個流水線; stream 提供了多種類型的中間操作,如 filter、distinct、map、sorted 等等;
② 終端操作
當(dāng)所有的中間操作完成后,若要將數(shù)據(jù)從流水線上拿下來,則需要執(zhí)行終端操作;
stream 對于終端操作,可以直接提供一個中間操作的結(jié)果,或者將結(jié)果轉(zhuǎn)換為特定的 collection、array、String 等;
stream 的特點
① 只能遍歷一次:
數(shù)據(jù)流的從一頭獲取數(shù)據(jù)源,在流水線上依次對元素進(jìn)行操作,當(dāng)元素通過流水線,便無法再對其進(jìn)行操作,可以重新在數(shù)據(jù)源獲取一個新的數(shù)據(jù)流進(jìn)行操作;
② 采用內(nèi)部迭代的方式:
對Collection進(jìn)行處理,一般會使用 Iterator 遍歷器的遍歷方式,這是一種外部迭代;
而對于處理Stream,只要申明處理方式,處理過程由流對象自行完成,這是一種內(nèi)部迭代,對于大量數(shù)據(jù)的迭代處理中,內(nèi)部迭代比外部迭代要更加高效;
stream 相對于 Collection 的優(yōu)點
無存儲:流并不存儲值;流的元素源自數(shù)據(jù)源(可能是某個數(shù)據(jù)結(jié)構(gòu)、生成函數(shù)或I/O通道等等),通過一系列計算步驟得到; 函數(shù)式風(fēng)格:對流的操作會產(chǎn)生一個結(jié)果,但流的數(shù)據(jù)源不會被修改; 惰性求值:多數(shù)流操作(包括過濾、映射、排序以及去重)都可以以惰性方式實現(xiàn)。這使得我們可以用一遍遍歷完成整個流水線操作,并可以用短路操作提供更高效的實現(xiàn); 無需上界:不少問題都可以被表達(dá)為無限流(infinite stream):用戶不停地讀取流直到滿意的結(jié)果出現(xiàn)為止(比如說,枚舉 完美數(shù) 這個操作可以被表達(dá)為在所有整數(shù)上進(jìn)行過濾);集合是有限的,但流可以表達(dá)為無線流; 代碼簡練:對于一些collection的迭代處理操作,使用 stream 編寫可以十分簡潔,如果使用傳統(tǒng)的 collection 迭代操作,代碼可能十分啰嗦,可讀性也會比較糟糕;
stream 和 iterator 迭代的效率比較
好了,上面 stream 的優(yōu)點吹了那么多,stream 函數(shù)式的寫法是很舒服,那么 steam 的效率到底怎樣呢?
先說結(jié)論:
傳統(tǒng) iterator (for-loop) 比 stream(JDK8) 迭代性能要高,尤其在小數(shù)據(jù)量的情況下; 在多核情景下,對于大數(shù)據(jù)量的處理,parallel stream 可以有比 iterator 更高的迭代處理效率;
我分別對一個隨機(jī)數(shù)列 List (數(shù)量從 10 到 10000000)進(jìn)行映射、過濾、排序、規(guī)約統(tǒng)計、字符串轉(zhuǎn)化場景下,對使用 stream 和 iterator 實現(xiàn)的運行效率進(jìn)行了統(tǒng)計
測試環(huán)境如下:
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
映射處理測試
把一個隨機(jī)數(shù)列(List
//stream
List<Integer> result = list.stream()
.mapToInt(x -> x)
.map(x -> ++x)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>();
for(Integer e : list){
result.add(++e);
}
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x -> x)
.map(x -> ++x)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
過濾處理測試
取出一個隨機(jī)數(shù)列(List
//stream
List<Integer> result = list.stream()
.mapToInt(x -> x)
.filter(x -> x > 200)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>(list.size());
for(Integer e : list){
if(e > 200){
result.add(e);
}
}
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x -> x)
.filter(x -> x > 200)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
自然排序測試
對一個隨機(jī)數(shù)列(List
//stream
List<Integer> result = list.stream()
.mapToInt(x->x)
.sorted()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>(list);
Collections.sort(result);
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x->x)
.sorted()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
歸約統(tǒng)計測試
獲取一個隨機(jī)數(shù)列(List
//stream
int max = list.stream()
.mapToInt(x -> x)
.max()
.getAsInt();
//iterator
int max = -1;
for(Integer e : list){
if(e > max){
max = e;
}
}
//parallel stream
int max = list.parallelStream()
.mapToInt(x -> x)
.max()
.getAsInt();
字符串拼接測試
獲取一個隨機(jī)數(shù)列(List
//stream
String result = list.stream().map(String::valueOf).collect(Collectors.joining(","));
//iterator
StringBuilder builder = new StringBuilder();
for(Integer e : list){
builder.append(e).append(",");
}
String result = builder.length() == 0 ? "" : builder.substring(0,builder.length() - 1);
//parallel stream
String result = list.stream().map(String::valueOf).collect(Collectors.joining(","));
混合操作測試
對一個隨機(jī)數(shù)列(List
//stream
List<Integer> result = list.stream()
.filter(Objects::nonNull)
.mapToInt(x -> x + 1)
.filter(x -> x > 200)
.distinct()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
HashSet<Integer> set = new HashSet<>(list.size());
for(Integer e : list){
if(e != null && e > 200){
set.add(e + 1);
}
}
List<Integer> result = new ArrayList<>(set);
//parallel stream
List<Integer> result = list.parallelStream()
.filter(Objects::nonNull)
.mapToInt(x -> x + 1)
.filter(x -> x > 200)
.distinct()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
實驗結(jié)果總結(jié)
從以上的實驗來看,可以總結(jié)處以下幾點:
在少低數(shù)據(jù)量的處理場景中(size<=1000),stream 的處理效率是不如傳統(tǒng)的 iterator 外部迭代器處理速度快的,但是實際上這些處理任務(wù)本身運行時間都低于毫秒,這點效率的差距對普通業(yè)務(wù)幾乎沒有影響,反而 stream 可以使得代碼更加簡潔; 在大數(shù)據(jù)量(szie>10000)時,stream 的處理效率會高于 iterator,特別是使用了并行流,在cpu恰好將線程分配到多個核心的條件下(當(dāng)然parallel stream 底層使用的是 JVM 的 ForkJoinPool,這東西分配線程本身就很玄學(xué)),可以達(dá)到一個很高的運行效率,然而實際普通業(yè)務(wù)一般不會有需要迭代高于10000次的計算; Parallel Stream 受引 CPU 環(huán)境影響很大,當(dāng)沒分配到多個cpu核心時,加上引用 forkJoinPool 的開銷,運行效率可能還不如普通的 Stream;
使用 Stream 的建議
簡單的迭代邏輯,可以直接使用 iterator,對于有多步處理的迭代邏輯,可以使用 stream,損失一點幾乎沒有的效率,換來代碼的高可讀性是值得的; 單核 cpu 環(huán)境,不推薦使用 parallel stream,在多核 cpu 且有大數(shù)據(jù)量的條件下,推薦使用 paralle stream; stream 中含有裝箱類型,在進(jìn)行中間操作之前,最好轉(zhuǎn)成對應(yīng)的數(shù)值流,減少由于頻繁的拆箱、裝箱造成的性能損失; 還有很多開發(fā)者不知道Stream不好調(diào)試,那么也可以看看這篇文章:Java 8的Stream操作不好調(diào)試?試試這個方法吧!
推薦閱讀
你好,我是程序猿DD,10年開發(fā)老司機(jī)、阿里云MVP、騰訊云TVP、出過書創(chuàng)過業(yè)、國企4年互聯(lián)網(wǎng)6年。從普通開發(fā)到架構(gòu)師、再到合伙人。一路過來,給我最深的感受就是一定要不斷學(xué)習(xí)并關(guān)注前沿。只要你能堅持下來,多思考、少抱怨、勤動手,就很容易實現(xiàn)彎道超車!所以,不要問我現(xiàn)在干什么是否來得及。如果你看好一個事情,一定是堅持了才能看到希望,而不是看到希望才去堅持。相信我,只要堅持下來,你一定比現(xiàn)在更好!如果你還沒什么方向,可以先關(guān)注我,這里會經(jīng)常分享一些前沿資訊,幫你積累彎道超車的資本。
