Java函數式編程之【流(Stream)性能優化
- 一、流(Stream)性能優化的預備知識
- (一)并行與并發的區別
- (二)Stream操作特性分類
- (三)Stream流管道的相關知識
- 二、流(Stream)的性能優化策略
- (一)選擇合適的Stream(流)類型
- 1,優先使用基本數據類型流(避免裝箱/拆箱開銷)
- 2,正確選擇 順序流(stream) 或 并行流(parallelStream)
- (二)中間操作的優化
- (三)終端操作的優化
- (四)正確使用并行流(parallelStream)
- 三、總結:流(Stream)性能優化的工作流程
一、流(Stream)性能優化的預備知識
我們先來詳細探討一下基于 Java Stream API 的函數式編程時性能調優相關的一些基礎知識。
(一)并行與并發的區別
在Java函數式編程中編寫并行程序很容易,要優化Java Stream程序的性能首先要搞清楚并行和并發的概念。
并發 (Concurrency):多個任務在同一時間段內執行,但不一定是同時執行。例如:可在單核CPU上并發運行多個線程。其執行方式是通過時間片輪轉,進行任務切換實現。
在單核CPU上的并發執行,邏輯上是"同時"執行,實質是串行輪流執行的。
并行 (Parallelism):多個任務同時執行,但需要有多核或多處理器硬件支持。例如:多核CPU同時處理多個不同任務。
(二)Stream操作特性分類
我們先來了解一下Stream操作分類,因為弄清Stream操作分類對大數據的高效迭代處理的性能優化有很大幫助。
中間操作和終止操作是Stream API中的關鍵概念。中間操作又可以細分為無狀態(Stateless)操作和有狀態(Stateful)操作;終結操作又可細分為短路(Short-circuiting)操作和非短路(Unshort-circuiting)操作。
- 中間操作:又稱為裝飾操作,它的輸入是一個Stream,操作后返回一個新的Stream。
中間操作只對操作進行了聲明,它會把Stream從一種流(Stream)轉換為另一種流,中間操作都是惰性的,它們不會立即執行。例如,filter(), map() 和 sorted() 等方法都屬于中間操作,它們會返回一個新的Stream實例。中間操作可以鏈接在一起,形成一個鏈式調用,成為Stream處理管道的組成部分。
無狀態(Stateless)操作和有狀態(Stateful)操作:
中間操作又可以分為無狀態(Stateless)與有狀態(Stateful)操作,前者是指元素的處理不受之前元素的影響,后者是指該操作只有拿到所有元素之后才能繼續下去。
1,常見無狀態(Stateless)的中間操作有:filter() 、map()、 flatMap()、 peek()
2,常見有狀態(Stateful)的中間操作有:distinct() 、sorted()、 limit() 、skip()
- 終止操作:又稱為終端操作,終止操作將觸發了整個流(Stream)的計算過程,得到操作結果。
終止操作則是主動求值操作。終止操作會觸發整個Stream處理管道的執行。例如,collect(), forEach(), reduce() 等方法都是終止操作。
當終止操作被調用時,所有的中間操作將按序執行,最終得到操作結果。
短路(Short-circuiting)操作和非短路(Unshort-circuiting)操作:
終結操作又可以分為短路(Short-circuiting)與非短路(Unshort-circuiting)操作,前者是指遇到某些符合條件的元素就可以得到最終結果,后者是指必須處理完所有元素才能得到最終結果。
短路(Short-circuiting): anyMatch()、 allMatch()、 findFirst()、 findAny()、 noneMatch()
非短路(Unshort-circuiting)操作:forEachOrdered()、toArray()、reduce()、collect()、max()、min()、count()
(三)Stream流管道的相關知識
函數式編程的一個流管道(Stream pipeline),包括Stream的創建(由數據源創建Stream)、0個或n個Stream的中間操作和一個Stream的終止操作。
如果不了解 流(Stream) 函數式編程的內部機制,就寫不錯高性能的程序。只有了解了流管道(Stream pipeline)的執行過程,我們才能更好地優化Stream的性能。
我們先來梳理一下 Stream API 是由哪些主要接口和類組合而成的呢?BaseStream 和 Stream 是最頂端的接口類。
- BaseStream接口主要定義了流的基本方法,例如,iterator()、spliterator()、isParallel()等;
- Stream接口則定義了一些流的常用操作方法,例如,filter()、map()、mapToLong()、flatMap()和collect()等。
- ReferencePipeline是一個抽象類,他通過定義內部類組裝了各種流的操作。它定義了Head、StatelessOp、StatefulOp三個內部類,實現了BaseStream與Stream的接口,繼承了它們的方法。
- Sink接口則定義了每個Stream操作之間關系的協議,它包含begin()、end()、cancellationRequested()、accpet()四個方法。ReferencePipeline最終會將整個Stream流操作組裝成一個調用鏈,而這條調用鏈上的各個Stream操作的上下關系就是通過Sink接口協議來定義實現的。
Stream操作的鏈接調用
一個流管道(Stream pipeline)可由Stream的各種操作組合而成,并最終由終止操作完成數據處理。
在JDK中每次的中間操作會用階段(Stage)命名。
流管道的鏈接調用結構通常是由ReferencePipeline類實現的,前文已介紹過ReferencePipeline包含了Head、StatelessOp、StatefulOp三種內部類。
來看一個簡單的示例:
package stream;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamDemo {public static void main(String[] args) {//字符串的"+"操作 //字符串拼接List<String> names = Arrays.asList("Bob","John","Mary");Optional<String> str = names.stream().filter(e->e.length()>=4).reduce((s1,s2)->s1+s2);System.out.println("拼接結果:"+str.orElse(""));}
}
Head類主要用來定義數據源操作,在初次調用names.stream()方法時,會初次加載Head對象,此時為加載數據源操作;接著加載的是中間操作,分別為無狀態中間操作StatelessOp對象和有狀態操作StatefulOp對象,此時的Stage并沒有執行,而是通過AbstractPipeline生成了一個中間操作Stage鏈表;當我們調用終止操作時,會生成一個最終的Stage,通過這個Stage觸發之前的中間操作,從最后一個Stage開始,遞歸產生一個Sink鏈。
二、流(Stream)的性能優化策略
(一)選擇合適的Stream(流)類型
1,優先使用基本數據類型流(避免裝箱/拆箱開銷)
這是最立竿見影的優化策略之一。因為 Stream<Integer>、Stream<Long>、Stream<Double> 等都屬于對象流 Stream<T> 。Integer、Long 和 Double 都是基本數據類型的包裝類,包裝類型的對象流 Stream<T> 會帶來巨大的 裝箱(Boxing) 和 拆箱(Unboxing) 開銷。
將基本數據類型(如 int、long或double)轉換為數值的包裝類型(如 Integer、Long和Double)稱為裝箱操作;反之稱為拆箱操作。
對于大數據的數值運算,裝箱和拆箱的計算開銷,以及包裝類型占用的額外存儲空間,會明顯降低程序的運算速度。
優化策略: 在大數據的數值計算和數理統計場景,對于 int, long, 和 double 等基本數據類型,應優先使用基本數據類型流 IntStream, LongStream 和 DoubleStream,以避免裝箱和拆箱的開銷。
這些流有一個最常用的匯總統計SummaryStatistics()方法,這個方法可得到一組統計數據:元素個數、匯總值、最大值、最小值和平均值。
Java語言為基本數據類型流專門內置了一些實用的方法:例如,都有內置的規約操作,包括average、count、max、min、sum。
這些方法都直接在原始數據類型上操作,避免了包裝類的裝箱和拆箱開銷。
當數據源為大數據的列表 List<Integer> 時的情形:
// 低效 - 存在裝箱開銷List<Integer> numbers = ...;int sum = numbers.stream().reduce(0, Integer::sum);// 高效 - 使用 IntStreamint sum = numbers.stream().mapToInt(Integer::intValue) // 轉換為 IntStream.sum(); // 直接在 int 上操作
更進一步: 如果數據源本身是基本數據類型的數組的情形,可直接使用 Arrays.stream() 創建Stream<Integer> 流:
int[] intArray = ...;IntStream intStream = Arrays.stream(intArray);
裝箱和不裝箱的性能比較測試例程:
public static void 裝箱與不裝箱Test() {// 裝箱版本測試long startTime = System.currentTimeMillis();List<Integer> list = new ArrayList<>();for (int i = 0; i < 10_000_000; i++) {list.add(i);}long sum = 0;for (Integer num : list) {sum += num;}long endTime = System.currentTimeMillis();long boxedTime = endTime - startTime;// 原始數據類型版本測試startTime = System.currentTimeMillis();int[] array = new int[10_000_000];for (int i = 0; i < array.length; i++) {array[i] = i;}sum = 0;for (int num : array) {sum += num;}endTime = System.currentTimeMillis();long primitiveTime = endTime - startTime;System.out.println("裝箱版本耗時: " + boxedTime + "ms");System.out.println("原始數據類型版本耗時: " + primitiveTime + "ms");}
裝箱與不裝箱的測試結果:
在大數據計算密集型任務中,避免裝箱操作通常可以獲大幅的性能提升,同時減少內存使用和GC壓力。
2,正確選擇 順序流(stream) 或 并行流(parallelStream)
順序流或并行流:根據數據集大小和是否對元素順序敏感選擇順序流stream()或并行流parallelStream()。小規模數據集或順序敏感的操作適合順序流;大規模數據集且能接受非確定性結果時適合并行流。
下面來看一個小規模數據集,我們分別使用基本數據類型流、順序流裝箱和并行流裝箱進行比較測試:
package stream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StreamTest {public static void primitive() { //基本數據類型流(順序流)long start = System.currentTimeMillis();long sum = IntStream.range(1, 10_000).sum();long duration = System.currentTimeMillis() - start;System.out.println("基本類型流求和結果: " + sum + ", 耗時: " + duration + "ms");}public static void boxed() { //順序流裝箱long start = System.currentTimeMillis();List<Integer> numbers = IntStream.range(1, 10_000).boxed().collect(Collectors.toList());int sum = numbers.stream().mapToInt(Integer::intValue).sum();long duration = System.currentTimeMillis() - start;System.out.println("順序流求和結果: " + sum + ", 耗時: " + duration + "ms");}public static void parallel() { //并行流裝箱long start = System.currentTimeMillis();List<Integer> numbers = IntStream.range(1, 10_000).boxed().collect(Collectors.toList());int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();long duration = System.currentTimeMillis() - start;System.out.println("并行流求和結果: " + sum + ", 耗時: " + duration + "ms");}public static void main(String[] args) {primitive();boxed();parallel();}
}
小規模數據集,我們分別使用基本數據類型流、順序流裝箱和并行流裝箱進行比較測試。
預期結果是耗時從小到大。但是令人遺憾的是,測試并未得到預期的結果。
如果調整代碼順序會得到完全不同的測試結果。測試順序嚴重影響結果。
原因是:Java 的 JIT(即時編譯器)需要時間來優化代碼。先執行的代碼都會因為JIT還沒有充分優化而變慢。
- 先執行的代碼:承擔 JIT 編譯的開銷。
- 后執行的代碼:享受已經優化好的代碼。
解決方案:正確的性能測試方法是使用專業的基準測試工具,如JMH(Java Microbenchmark Harness)。
下面這個示例,試圖演示在數值運算場景中,基本數據類型流LongStream 比對象流Stream有更好的效率。
使用專業的基準測試工具(如 JMH),才能得到準確的結果。你會發現 LongStream 確實比 Stream 快 2-5 倍。
package stream;
import java.util.Random;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public class BOxUnboxingComparison {private static final int DATA_SIZE = 10_000;private static final Random RANDOM1 = new Random(42);private static final Random RANDOM2 = new Random(42);public static void main(String[] args) {// 測試1: 基本的統計操作System.out.println("=== 基本統計操作性能測試 ===");/***對象流Stream(Long),數據計算時需要拆箱***/long startTime = System.nanoTime();long boxedSum = Stream.generate(() -> Math.abs(RANDOM1.nextLong()) % 100).limit(DATA_SIZE).mapToLong(Long::longValue) // 必須拆箱才能計算.sum();long boxedTime = System.nanoTime() - startTime;/***基本數據類型流LongStream,數據計算時無須拆箱***/startTime = System.nanoTime();long primSum = LongStream.generate(() -> Math.abs(RANDOM2.nextLong()) % 100).limit(DATA_SIZE).sum();long primTime = System.nanoTime() - startTime;printResults("求和", primTime, boxedTime, primSum, boxedSum);// 測試2: 過濾和聚合操作System.out.println("\n=== 過濾和聚合操作性能測試 ===");/***對象流Stream(Long),數據計算時需要拆箱***/startTime = System.nanoTime();double boxedAvg = Stream.generate(() -> Math.abs(RANDOM1.nextLong()) % 100).limit(DATA_SIZE).filter(x -> x > 50) // 過濾(自動拆箱比較).mapToLong(Long::longValue) // 必須拆箱.average().orElse(0);boxedTime = System.nanoTime() - startTime;/***基本數據類型流LongStream,數據計算時無須拆箱***/startTime = System.nanoTime();double primAvg = LongStream.generate(() -> Math.abs(RANDOM2.nextLong()) % 100).limit(DATA_SIZE).filter(x -> x > 50) // 過濾.average() // 計算平均值.orElse(0);primTime = System.nanoTime() - startTime;printResults("平均值", primTime, boxedTime, primAvg, boxedAvg);}private static void printResults(String operation, long primitiveTime, long boxedTime, Object primitiveResult, Object boxedResult) {System.out.printf("LongStream - 耗時: %8d ns, 結果: %s%n", primitiveTime, primitiveResult);System.out.printf("Stream<Long> - 耗時: %8d ns, 結果: %s%n", boxedTime, boxedResult);System.out.printf("性能差距: %.2f 倍%n", (double) boxedTime / primitiveTime);}
}
(二)中間操作的優化
每個中間操作都可能涉及到對元素的復制和創建新的Stream對象。合理配置中間操作可優化Stream的性能。
- 使用 distinct() 刪除重復元素
如果 stream 中可能包含重復元素,可使用 distinct() 操作將其刪除,防止無效處理以提高性能。
List<Integer> list = Arrays.asList(1, 2, 3, 3, 4, 5, 5);list.stream().distinct().forEach(System.out::println);
- 合理配置 limit() 防止無效處理
示例:
// 生成斐波那契數列但只取前10個Stream.iterate(new int[]{0, 1}, fib -> new int[]{fib[1], fib[0] + fib[1]}).limit(10) // 限制器.map(fib -> fib[0]).forEach(System.out::println);
- 謹慎使用 sorted()
sorted()操作可能很昂貴,尤其是對于大數據流(large streams)。請謹慎使用,只有在必要時才使用。如果知道輸入數據已經排序,可以跳過此操作。
- 選擇正確的中間操作順序。需要數據過濾的場景,盡早使用filter()
中間操作的順序對性能有巨大影響,尤其是 filter() 和 map() 的順序。
優化策略:
合理配置map()和filter()的組合。先過濾filter(),后映射map(): 盡可能早地使用 filter() 篩選掉不需要處理的元素。這會使后續所有操作(map, sorted, collect 等)的數據量變小。
示例:
var list = Arrays.asList(1, 2, 3, 4, 5,8,16,18);var filteredList = list.stream().filter(i -> i % 2 == 0).map(i -> i * 2).collect(Collectors.toList());
上面這個示例“在 map() 之前使用 filter()”可過濾掉一大半的元素,大大減少了map()操作的工作量。
避免在中間操作中執行昂貴操作: 如果 map 中包含一個昂貴的方法調用,確保它只在必要的元素上執行(即先被 filter 處理過的)。
// 低效的中間操作順序List<String> names = list.stream().map(e -> expensiveOperation(e)) // 對所有元素執行昂貴操作.filter(s -> s.length() > 3).collect(Collectors.toList());// 高效的中間操作順序 - 優先過濾List<String> names = list.stream().filter(e -> e.length() > 3) // 先減少數量.map(e -> expensiveOperation(e)) // 只對過濾后的元素執行.collect(Collectors.toList());
再來看另一個典型示例:
// 目標:獲取所有字符串長度的平方,且只保留大于10的結果// 低效:先map所有,再filter
List<Integer> lengthsSquared = words.stream().map(String::length) // 所有元素都被映射.map(len -> len * len) // 所有元素再次被映射.filter(sq -> sq > 10) // 最后過濾掉大部分.collect(Collectors.toList());// 更高效:合并map和filter,但邏輯稍復雜
// (有時無法避免,但這里可以優化)// 最優:合并map操作,并盡早filter
List<Integer> lengthsSquared = words.stream().map(String::length) // 先map一次.filter(len -> len > 3) // 盡早過濾:因為如果length<=3,平方肯定<=9,不會大于10.map(len -> len * len) // 只為剩余的元素進行平方計算.collect(Collectors.toList());
- 適當合并中間操作減少Stream內部迭代次數
1,多個連續的 filter() 操作合并。
經典示例:篩選用戶列表
合并多個filter操作是一個非常重要且常見的優化策略。它的核心思想是:通過邏輯運算符將多個過濾條件合并為一個filter,減少Stream管道中的階段數量和 predicate(斷言)的執行次數。
假設我們有一個User對象列表,我們需要篩選出同時滿足以下條件的用戶:
(1), 年齡大于等于18歲(成年人)
(2), 年齡小于65歲(未退休)
(3), 賬戶狀態是激活的(ACTIVE)
(4), 登錄次數大于0(活躍用戶)
低效算法: 使用多個連續的 filter() 操作
在這個算法中,數據仿佛要“過四道篩子”:
List<User> users = // ... 獲取用戶列表List<User> filteredUsers = users.stream().filter(user -> user.getAge() >= 18) // 第一輪過濾:檢查條件1.filter(user -> user.getAge() < 65) // 第二輪過濾:檢查條件2.filter(user -> user.getStatus() == User.Status.ACTIVE) // 第三輪過濾:檢查條件3.filter(user -> user.getLoginCount() > 0) // 第四輪過濾:檢查條件4.collect(Collectors.toList());
高效做法:合并 filter() 操作
List<User> users = // ... 獲取用戶列表List<User> filteredUsers = users.stream().filter(user -> user.getAge() >= 18 && user.getAge() < 65&& user.getStatus() == User.Status.ACTIVE&& user.getLoginCount() > 0).collect(Collectors.toList());
2,多個可合并的映射(map)操作合并
將多個可合并的映射(map)操作合并為一個,從而減少Stream管道中需要執行的迭代次數和中間對象的創建。
經典示例:處理字符串列表,假設我們有一個字符串列表,我們需要:
(1)將每個字符串轉換為大寫。
(2)反轉每個字符串。
(3)獲取每個字符串的前3個字符。
低效做法:使用多個連續的 map 操作。
在這個算法中,Stream管道內部發生了3次完整的迭代,并創建了2個中間的Stream元素集合。
List<String> words = Arrays.asList("hello", "world", "java", "stream");List<String> result = words.stream().map(String::toUpperCase) // 第一次迭代:生成新流 ["HELLO", "WORLD", "JAVA", "STREAM"].map(s -> new StringBuilder(s).reverse().toString()) // 第二次迭代:生成新流 ["OLLEH", "DLROW", "AVAJ", "MAERTS"].map(s -> s.substring(0, Math.min(3, s.length()))) // 第三次迭代:生成新流 ["OLL", "DLR", "AVA", "MAE"].collect(Collectors.toList());System.out.println(result); // 輸出: [OLL, DLR, AVA, MAE]
高效做法:合并中間操作
List<String> words = Arrays.asList("hello", "world", "java", "stream");List<String> result = words.stream().map(s -> { // 只進行一次映射操作,將所有轉換邏輯合并String upper = s.toUpperCase();String reversed = new StringBuilder(upper).reverse().toString();return reversed.substring(0, Math.min(3, reversed.length()));}).collect(Collectors.toList());System.out.println(result); // 輸出: [OLL, DLR, AVA, MAE]
(三)終端操作的優化
- 選擇高效的終端操作
不同的終端操作有不同的性能特征。優化策略:
(1)、anyMatch/allMatch/noneMatch 是短路(short-circuiting)操作,一旦找到答案就會停止處理,性能通常很好。
(2)、findFirst 在并行流中需要協調,可能會有一定開銷。如果順序不重要,findAny 在并行流中限制更少,性能可能更高。
(3)、count 對于 SIZED 流(如從 List 創建的流)來說非常高效。否則,它需要遍歷所有元素。
(4)、collect 的性能取決于你使用的收集器。Collectors.toList() 通常很高效。Collectors.groupingBy 和 Collectors.toMap 的成本較高,需要注意。
流管道(Stream pipeline)中的操作應該都是無副作用的。
例如:在中間操作中修改外部狀態會導致并發問題(尤其在并行流中)和不可預測的行為。
終端操作也應該是無副作用的。應優先使用收集器(Collectors) 而不是在 forEach 中修改外部集合。forEach 應僅用于消費最終結果,而不是處理過程。請看示例:
// 錯誤 - 在 forEach 中修改外部集合(非線程安全)
List<String> result = new ArrayList<>();
stream.filter(...).forEach(e -> result.add(e)); // 在并行流中會崩潰// 正確 - 使用收集器
List<String> result = stream.filter(...).collect(Collectors.toList()); // 線程安全且高效
- 利用短路操作提高效率
諸如 findFirst(),findAny() 和 anyMatch() 等短路操作,在找到符合條件或不符合條件時就會立即返回,不處理整個流。利用這些操作可以提高性能。
/***權限檢查短路***/
List<Permission> userPermissions = getUserPermissions(userId);
// 檢查用戶是否有管理員權限
boolean isAdmin = userPermissions.stream().anyMatch(p -> p.getType() == PermissionType.ADMIN); // 找到第一個管理員權限就返回
// 比收集到列表再判斷更高效/***驗證輸入數據***/
List<String> inputs = getInputs();
// 驗證所有輸入是否有效(遇到第一個無效就停止)
boolean allValid = inputs.stream().peek(input -> System.out.println("Validating: " + input)).allMatch(this::isValidInput);
(四)正確使用并行流(parallelStream)
Java的Stream API 支持并行流(parallelStream)操作,可以利用多個處理器陣列或多核處理器并行處理數據。
并行流操作通過將數據分割成多個子集,然后并行處理,最終合并結果。
并行流(parallelStream)可以在處理大量數據時提供更好的性能,但也會帶來額外開銷和競爭條件。謹慎使用parallelStream,并需要考慮數據規模、操作復雜程度和可用處理器數量等因素。
并行操作引入了線程管理的開銷和線程間協調開銷。如果使用不當,也可能引起線程管理的開銷增加。例如:在處理 IO 密集型任務或數據量小的場景,并行流可能因線程管理和線程間協調的開銷反而更慢。
當調用 stream().parallel() 或 parallelStream() 時進行并行處理時,Java 會將數據分割成多個子集,由 ForkJoinPool 分配線程并行處理。這種機制在處理“大數據集、CPU 密集型操作”時優勢明顯,比如統計分析、數據匯總,以及排序、過濾、聚合等操作。
并行流需要有硬件支持:多個處理器或多核處理器的系統
并行流 (parallelStream) 可以將工作負載分配到多個 CPU 核心上,但并非萬能藥。錯誤使用會導致更差的性能(線程上下文切換開銷)甚至錯誤結果。
優化策略:
使用工具測量,不要憑猜測: 始終使用類似 JMH 的工具進行基準測試,比較并行和串行流的性能。并行化的開銷(拆分、協調、合并)只有在數據量足夠大、計算足夠密集時才能被抵消。
并行流(parallelStream)適用場景:
- 大數據,大規模數據源(例如,數十萬以上元素)。
- 每個元素的處理是計算密集型(CPU-intensive)的,而不是 I/O 密集型(如網絡請求、文件讀寫)。
- 數據源易于拆分:ArrayList、數組、IntStream.range() 等支持隨機訪問的數據源拆分效率極高。而 LinkedList、Stream.iterate() 則難以有效拆分。
- 操作是無狀態且無關聯的(如 map, filter),合并操作成本低(如 reduce 的合并器)。
不適合的場景:
- 數據量小。
- 操作是 I/O 密集型(線程會大部分時間在等待,浪費資源,應使用專門的異步 API如 CompletableFuture)。
- 操作有狀態或有副作用(如 sorted, distinct, limit 在并行流中代價更高,因為需要協調)。
- 使用了有狀態的 Lambda 表達式或阻塞操作。
示例:
List<String> hugeList = ...;// 好的并行用例:大數據集,計算密集型操作long count = hugeList.parallelStream().filter(s -> s.length() > 10) // 無狀態.count(); // 簡單合并// 壞的并行用例:小數據集long count = smallList.parallelStream() // 開銷大于收益.count();
三、總結:流(Stream)性能優化的工作流程
-
首先,寫出正確、清晰的程序代碼: 不要過早優化。先使用 Stream API 實現正確的邏輯。
-
基準測試: 使用 JMH (Java Microbenchmark Harness) 等工具來識別真正的性能瓶頸。不要靠猜測。
-
應用調優策略進行性能優化:
第一步: 檢查是否能優先使用基本數據類型流 (IntStream 等)。
第二步: 中間操作的優化,檢查中間操作順序,例如確保盡早過濾 (filter)。
第三步: 評估是否適合并行化 (parallelStream),并進行測試。
第四步(高級): 檢查終端操作和收集器,必要時考慮自定義。
再次測量: 每次優化后都進行基準測試,確認優化確實有效。
通過遵循這些策略,才能最大限度地發揮 Java Stream API 函數式編程的威力,同時保持代碼的高性能。