恕我直言你可能真的不會java第6篇:Stream性能差?不要人云亦云

1、粉絲的反饋

問:stream比for循環慢5倍,用這個是爲了啥?
答:互聯網是一個新聞氾濫的時代,三人成虎,以假亂真的事情時候發生。做爲一個技術開發者,要本身去動手去作,不要人云亦云。vue

的確,這位粉絲說的這篇文章我也看過,我就不貼地址了,也不必給他帶流量。怎麼說呢?就是一個不懂得測試的、不入流開發工程師作的性能測試,給出了一個危言聳聽的結論。java

2、全部性能測試結論都是片面的

性能測試是必要的,但針對性能測試的結果,永遠要持懷疑態度。爲何這麼說?git

  • 性能測試脫離業務場景就是片面的性能測試。你能覆蓋全部的業務場景麼?
  • 性能測試脫離硬件環境就是片面的性能測試。你能覆蓋全部的硬件環境麼?
  • 性能測試脫離開發人員的知識面就是片面的性能測試。你能覆蓋各類開發人員奇奇怪怪的代碼麼?

因此,我歷來不相信網上的任何性能測試的文章。凡是我本身的從事的業務場景,我都要在接近生產環境的機器上本身測試一遍。 全部性能測試結論都是片面的,只有你生產環境下的運行結果纔是真的。github

3、動手測試Stream的性能

3.1.環境

windows10 、16G內存、i7-7700HQ 2.8HZ 、64位操做系統、JDK 1.8.0_171spring

3.2.測試用例與測試結論

咱們在上一節,已經講過:windows

  • 針對不一樣的數據結構,Stream流的執行效率是不同的
  • 針對不一樣的數據源,Stream流的執行效率也是不同的

因此記住筆者的話:全部性能測試結論都是片面的,你要本身動手作,相信你本身的代碼和你的環境下的測試!個人測試結果僅僅表明我本身的測試用例和測試數據結構!後端

3.2.1.測試用例一

測試用例:5億個int隨機數,求最小值
測試結論(測試代碼見後文):api

  • 使用普通for循環,執行效率是Stream串行流的2倍。也就是說普通for循環性能更好。
  • Stream並行流計算是普通for循環執行效率的4-5倍。
  • Stream並行流計算 > 普通for循環 > Stream串行流計算

3.2.測試用例二

測試用例:長度爲10的1000000隨機字符串,求最小值
測試結論(測試代碼見後文):springboot

  • 普通for循環執行效率與Stream串行流不相上下
  • Stream並行流的執行效率遠高於普通for循環
  • Stream並行流計算 > 普通for循環 = Stream串行流計算

3.3.測試用例三

測試用例:10個用戶,每人200個訂單。按用戶統計訂單的總價。
測試結論(測試代碼見後文):服務器

  • Stream並行流的執行效率遠高於普通for循環
  • Stream串行流的執行效率大於等於普通for循環
  • Stream並行流計算 > Stream串行流計算 >= 普通for循環

4、最終測試結論

  • 對於簡單的數字(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>類型進行遍歷?誰的生產服務器是單核?。

5、測試代碼

<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;
    }
}

歡迎關注個人博客,裏面有不少精品合集

  • 本文轉載註明出處(必須帶鏈接,不能只轉文字):字母哥博客

以爲對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創做動力! 。另外,筆者最近一段時間輸出了以下的精品內容,期待您的關注。