
下面是視頻(優酷的清晰度有限):還是建議大家去B站觀看:[B站觀看地址](https://www.bilibili.com/video/BV1sE411P7C1/)。如果您覺得我做的工作對您有幫助,請去B站點贊、關注、轉發、收藏,您的支持是我不竭的創作動力!
```[youku]
XNDYxMzYyODYzMg
```
## 一、粉絲的反饋

**問:stream比for循環慢5倍,用這個是為了啥?**
答:互聯網是一個新聞泛濫的時代,三人成虎,以假亂真的事情時候發生。作為一個技術開發者,要自己去動手去做,不要人云亦云。
的確,這位粉絲說的這篇文章我也看過,我就不貼地址了,也沒必要給他帶流量。怎么說呢?就是一個不懂得測試的、不入流開發工程師做的性能測試,給出了一個危言聳聽的結論。
## 二、所有性能測試結論都是片面的
性能測試是必要的,但針對性能測試的結果,永遠要持懷疑態度。為什么這么說?
* 性能測試脫離業務場景就是片面的性能測試。你能覆蓋所有的業務場景么?
* 性能測試脫離硬件環境就是片面的性能測試。你能覆蓋所有的硬件環境么?
* 性能測試脫離開發人員的知識面就是片面的性能測試。你能覆蓋各種開發人員奇奇怪怪的代碼么?
所以,我從來不相信網上的任何性能測試的文章。凡是我自己的從事的業務場景,我都要在接近生產環境的機器上自己測試一遍。 **所有性能測試結論都是片面的,只有你生產環境下的運行結果才是真的。**
## 三、動手測試Stream的性能
### 3.1.環境
windows10 、16G內存、i7-7700HQ 2.8HZ 、64位操作系統、JDK 1.8.0_171
### 3.2.測試用例與測試結論
我們在上一節,已經講過:
* 針對不同的數據結構,Stream流的執行效率是不一樣的
* 針對不同的數據源,Stream流的執行效率也是不一樣的
所以記住筆者的話:**所有性能測試結論都是片面的**,你要自己動手做,相信你自己的代碼和你的環境下的測試!我的測試結果僅僅代表我自己的測試用例和測試數據結構!
#### 3.2.1.測試用例一
測試用例:5億個int隨機數,求最小值
測試結論(測試代碼見后文):
* 使用普通for循環,執行效率是Stream串行流的2倍。也就是說普通for循環性能更好。
* Stream并行流計算是普通for循環執行效率的4-5倍。
* Stream并行流計算 > 普通for循環 > Stream串行流計算

### 3.2.測試用例二
測試用例:長度為10的1000000隨機字符串,求最小值
測試結論(測試代碼見后文):
* 普通for循環執行效率與Stream串行流不相上下
* Stream并行流的執行效率遠高于普通for循環
* Stream并行流計算 > 普通for循環 = Stream串行流計算

### 3.3.測試用例三
測試用例:10個用戶,每人200個訂單。按用戶統計訂單的總價。
測試結論(測試代碼見后文):
* Stream并行流的執行效率遠高于普通for循環
* Stream串行流的執行效率大于等于普通for循環
* Stream并行流計算 > Stream串行流計算 >= 普通for循環

## 四、最終測試結論
* 對于簡單的數字(list-Int)遍歷,普通for循環效率的確比Stream串行流執行效率高(1.5-2.5倍)。但是Stream流可以利用并行執行的方式發揮CPU的多核優勢,因此并行流計算執行效率高于for循環。
* 對于list-Object類型的數據遍歷,普通for循環和Stream串行流比也沒有任何優勢可言,更不用提Stream并行流計算。
雖然在不同的場景、不同的數據結構、不同的硬件環境下。Stream流與for循環性能測試結果差異較大,甚至發生逆轉。**但是總體上而言**:
* Stream并行流計算 >> 普通for循環 ~= Stream串行流計算 (之所以用兩個大于號,你細品)
* 數據容量越大,Stream流的執行效率越高。
* Stream并行流計算通常能夠比較好的利用CPU的多核優勢。CPU核心越多,Stream并行流計算效率越高。
stream比for循環慢5倍?也許吧,單核CPU、串行Stream的int類型數據遍歷?我沒試過這種場景,但是我知道這不是應用系統的核心場景。看了十幾篇測試博文,和我的測試結果。我的結論是: **在大多數的核心業務場景下及常用數據結構下,Stream的執行效率比for循環更高。** 畢竟我們的業務中通常是實實在在的實體對象,沒事誰總對`List<Int>`類型進行遍歷?誰的生產服務器是單核?。
## 五、測試代碼
~~~
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>junitperf</artifactId>
<version>2.0.0</version>
</dependency>
~~~
測試用例一:
~~~
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;
import java.util.Arrays;
import java.util.Random;
public class StreamIntTest {
public static int[] arr;
@BeforeAll
public static void init() {
arr = new int[500000000]; //5億個隨機Int
randomInt(arr);
}
@JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
public void testIntFor() {
minIntFor(arr);
}
@JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
public void testIntParallelStream() {
minIntParallelStream(arr);
}
@JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
public void testIntStream() {
minIntStream(arr);
}
private int minIntStream(int[] arr) {
return Arrays.stream(arr).min().getAsInt();
}
private int minIntParallelStream(int[] arr) {
return Arrays.stream(arr).parallel().min().getAsInt();
}
private int minIntFor(int[] arr) {
int min = Integer.MAX_VALUE;
for (int anArr : arr) {
if (anArr < min) {
min = anArr;
}
}
return min;
}
private static void randomInt(int[] arr) {
Random r = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = r.nextInt();
}
}
}
~~~
測試用例二:
~~~
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;
import java.util.ArrayList;
import java.util.Random;
public class StreamStringTest {
public static ArrayList<String> list;
@BeforeAll
public static void init() {
list = randomStringList(1000000);
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testMinStringForLoop(){
String minStr = null;
boolean first = true;
for(String str : list){
if(first){
first = false;
minStr = str;
}
if(minStr.compareTo(str)>0){
minStr = str;
}
}
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void textMinStringStream(){
list.stream().min(String::compareTo).get();
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testMinStringParallelStream(){
list.stream().parallel().min(String::compareTo).get();
}
private static ArrayList<String> randomStringList(int listLength){
ArrayList<String> list = new ArrayList<>(listLength);
Random rand = new Random();
int strLength = 10;
StringBuilder buf = new StringBuilder(strLength);
for(int i=0; i<listLength; i++){
buf.delete(0, buf.length());
for(int j=0; j<strLength; j++){
buf.append((char)('a'+ rand.nextInt(26)));
}
list.add(buf.toString());
}
return list;
}
}
~~~
測試用例三:
~~~
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;
import java.util.*;
import java.util.stream.Collectors;
public class StreamObjectTest {
public static List<Order> orders;
@BeforeAll
public static void init() {
orders = Order.genOrders(10);
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testSumOrderForLoop(){
Map<String, Double> map = new HashMap<>();
for(Order od : orders){
String userName = od.getUserName();
Double v;
if((v=map.get(userName)) != null){
map.put(userName, v+od.getPrice());
}else{
map.put(userName, od.getPrice());
}
}
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testSumOrderStream(){
orders.stream().collect(
Collectors.groupingBy(Order::getUserName,
Collectors.summingDouble(Order::getPrice)));
}
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
public void testSumOrderParallelStream(){
orders.parallelStream().collect(
Collectors.groupingBy(Order::getUserName,
Collectors.summingDouble(Order::getPrice)));
}
}
class Order{
private String userName;
private double price;
private long timestamp;
public Order(String userName, double price, long timestamp) {
this.userName = userName;
this.price = price;
this.timestamp = timestamp;
}
public String getUserName() {
return userName;
}
public double getPrice() {
return price;
}
public long getTimestamp() {
return timestamp;
}
public static List<Order> genOrders(int listLength){
ArrayList<Order> list = new ArrayList<>(listLength);
Random rand = new Random();
int users = listLength/200;// 200 orders per user
users = users==0 ? listLength : users;
ArrayList<String> userNames = new ArrayList<>(users);
for(int i=0; i<users; i++){
userNames.add(UUID.randomUUID().toString());
}
for(int i=0; i<listLength; i++){
double price = rand.nextInt(1000);
String userName = userNames.get(rand.nextInt(users));
list.add(new Order(userName, price, System.nanoTime()));
}
return list;
}
@Override
public String toString(){
return userName + "::" + price;
}
}
~~~
- 前言
- 1.lambda表達式會用了么
- 2.初識Stream-API
- 3.Stream的filter與謂語邏輯
- 4.Stream管道流的map操作
- 5.Stream的狀態與并行操作
- 6.Stream性能差?不要人云亦云
- 7.像使用SQL一樣排序集合
- 8.函數式接口Comparator
- 9.Stream查找與匹配元素
- 10.Stream集合元素歸約
- 11.StreamAPI終端操作
- 12.java8如何排序Map
- Stream流逐行文件處理
- java8-forEach(持續發布中)
- 筆者其它作品推薦
- vue深入淺出系列
- 手摸手教你學Spring Boot2.0
- Spring Security-JWT-OAuth2一本通
- 實戰前后端分離RBAC權限管理系統
- 實戰SpringCloud微服務從青銅到王者