Java函數式編程之【流(Stream)性能優化】

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>IntegerLongDouble 都是基本數據類型的包裝類,包裝類型的對象流 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 函數式編程的威力,同時保持代碼的高性能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/97381.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/97381.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/97381.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Cybero: 1靶場滲透

Cybero: 1 來自 <Cybero: 1 ~ VulnHub> 1&#xff0c;將兩臺虛擬機網絡連接都改為NAT模式 2&#xff0c;攻擊機上做namp局域網掃描發現靶機 nmap -sn 192.168.23.0/24 那么攻擊機IP為192.168.23.128&#xff0c;靶場IP192.168.23.139 3&#xff0c;對靶機進行端口服務探…

【學習筆記】非異步安全函數(禁止在信號處理中調用)

非異步安全函數&#xff08;禁止在信號處理中調用&#xff09; 一、測試 在信號處理函數&#xff08;Signal Handler&#xff09;中&#xff0c;只有異步信號安全函數&#xff08;async-signal-safe functions&#xff09; 可以安全調用。這類函數的特點是&#xff1a;不使用全…

【K8s】整體認識K8s之K8s的控制器

作用&#xff1a;控制器的作用就是持續監控k8s集群的狀態&#xff0c;讓它處于我們期望的狀態&#xff0c;常見的控制器有replicaset、deployment、daemonset、statefulset 、job 、cronjobReplicaset控制一組pod的副本數&#xff0c;始終與預設的值相同&#xff0c;會持續監視…

R ggplot2學習Nature子刊一張圖,換數據即可用!

本次使用R語言復現Nature Communications上的1張組合圖,這張圖兼具顏值+節約版面! Fig. 1 b原圖 ??復現效果圖-b圖?? ?讀入測試數據! ?關鍵代碼, # 關鍵代碼 library(ggplot2) library(dplyr) library(cowplot)# --- 外圈圖 --- p_outer <- ggplot(data_aug, aes…

迷你電腦用到什么型號的RJ45網口

迷你電腦常用的 RJ45 網口主要有標準 RJ45 網口和 Mini RJ45 網口兩種。標準 RJ45 網口是最常見的類型&#xff0c;遵循 IEEE 802.3i 標準&#xff0c;采用 8P8C&#xff08;8 Position 8 Contact&#xff0c;8 位 8 觸點&#xff09;連接器&#xff0c;有 T568A 和 T568B 兩種…

網絡安全 | 保護智能家居和企業IoT設備的安全策略

網絡安全 | 保護智能家居和企業IoT設備的安全策略 一、前言 二、智能家居和企業 IoT 設備面臨的安全威脅 2.1 設備自身安全缺陷 2.2 網絡通信安全隱患 2.3 數據隱私風險 2.4 惡意軟件和攻擊手段 三、保護智能家居和企業 IoT 設備的安全策略 3.1 設備安全設計與制造環節的考量 3…

優化器全指南:從原理到調優實戰

本文將帶你輕松理解深度學習中的“導航系統”——優化器。我們會避開復雜的數學公式,用大量的比喻和圖示,讓你徹底明白 Adam、AdamW、LAMB 是怎么回事,并學會如何調節它們的關鍵參數。 第一部分:核心概念:優化器是什么? 一個簡單的比喻: 想象你在一座大霧彌漫的山里(…

Notepad++使用技巧1

1.打開官方參考代碼經常看到下圖這種行尾很多空格的代碼&#xff0c;一點都不合符華為的書寫規范&#xff0c;閱讀起來容易讓人煩躁不安。初學者建議看看華為的代碼書寫規范&#xff0c;你將少走很多彎路&#xff0c;終生受益。2.快速去掉行尾很多空格方法點擊頂部菜單欄“宏”…

AIoT云邊協同方式

隨著物聯網&#xff08;IoT&#xff09;與人工智能&#xff08;AI&#xff09;的深度融合&#xff0c;AIoT&#xff08;人工智能物聯網&#xff09;作為一種新興技術范式&#xff0c;正在推動智能設備與產業的快速發展。AIoT通過云邊協同的方式&#xff0c;將邊緣側的IoT設備、…

MIT 6.5840 (Spring, 2024) 通關指南——Lab 1: MapReduce

MIT 6.5840 (Spring, 2024) – Lab 1: MapReduce &#x1f468;?&#x1f4bb; Charles &#x1f517; 實驗手冊&#xff1a; 6.5840 Lab 1: MapReduce &#x1f4c3; MapReduce 論文原文&#xff1a; mapreduce-osdi04.pdf ?? 本系列前文&#xff1a; MIT 6.5840 (Spring, …

吳恩達機器學習作業五:神經網絡正向傳播

數據集在作業一正向傳播正向傳播&#xff08;Forward Propagation&#xff09;是神經網絡計算過程中的核心步驟&#xff0c;指的是將輸入數據通過神經網絡的各層依次傳遞&#xff0c;最終得到輸出結果的過程。核心原理在神經網絡中&#xff0c;信息從輸入層流入&#xff0c;經過…

網絡編程(4)

【0】復習 sockfdsocket(); //指定網絡信息 bind(); listen(); //創建表 fd_set rfds,tempfds; FD_ZERO(); FD_SET(sockfd); max sockfd while(1) {tempfdsrfds;select(max1,&tempfds)if(FD_ISSET(scokfd,&tempfds)){acceptfdaccept();FD_SET(acceptfd,&rfds);if(m…

Windows系統提示“找不到文件‘javaw‘”

1. Java 未安裝或安裝不完整javaw.exe 是 Java 運行環境&#xff08;JRE&#xff09;的核心文件&#xff0c;用于運行 Java 程序&#xff08;如.jar 文件&#xff09;。如果你的電腦沒有安裝 Java&#xff0c;或安裝過程中 javaw.exe 被誤刪&#xff0c;系統就會找不到它。2. J…

【PCIE系列】1---PCIE系統拓撲結構分析

架構由點對點鏈路&#xff08;Links&#xff09;組成&#xff0c;用于互連組成系統的一系列組件。下圖展示了一個示例拓撲結構。該圖描述了一個有層次的體系架構實例&#xff0c;其包含根復合體&#xff08;Root Complex, RC&#xff09;、多個端點&#xff08;I/O設備&#xf…

SpringBoot防止重復提交(2)

例如&#xff1a;多次點擊提現按鈕問題描述&#xff1a;在提現操作中&#xff0c;用戶可能會多次點擊提現按鈕&#xff0c;導致多個相同的請求發送到服務器&#xff0c;從而引發重復提現的問題。為了解決這一問題&#xff0c;必須保證每個提現請求只能執行一次&#xff0c;防止…

mysql zip包安裝步驟

下載地址 windows MSI Install 安裝包程序。 這里下載zip包&#xff0c;執行安裝過程 確認my.ini 配置的路徑&#xff0c;創建mysql數據服務的data目錄管理員身份cmd 進入bin目錄&#xff0c;開始初始化服務 mysqld --initialize-insecure --usermysql mysqld -install#啟動…

Python 的 argparse 模塊中,add_argument 方法的 nargs 參數

在 Python 的 argparse 模塊中&#xff0c;add_argument 方法的 nargs 參數用于指定命令行參數可以接受的參數數量。你提到的 nargs* 和 nargs 是兩種常見設置&#xff0c;它們分別表示不同的參數數量要求。以下是兩者的詳細區別和含義&#xff1a;1. nargs*: 接受零個或多個參…

嵌入式Linux LED驅動開發

嵌入式Linux LED驅動開發 一、LED驅動概述 本筆記基于IMX6ULL處理器的LED驅動開發&#xff0c;詳細介紹了字符設備驅動開發的基本流程。該驅動實現了對LED的基本控制功能&#xff0c;通過字符設備接口供用戶空間程序調用。 二、LED驅動核心概念 1. 寄存器地址定義 本驅動涉…

Excel Word Pdf 格式轉換

引入aspose包手動更新本地mvn倉庫mvn install:install-file -DfileC:\aspose-cells-22.9.jar -DgroupIdaspose -DartifactIdaspose-cells -Dversion22.9 -Dpackagingjar mvn install:install-file -DfileC:\aspose-pdf-22.9.jar -DgroupIdaspose -DartifactIdaspose-pdf -Dvers…

變頻器實習DAY40 調整測試零伺服PI LDO

目錄變頻器實習DAY40一、工作內容1.1 調整測試零伺服PI二、學習內容2.1 LDOLDO的核心工作原理——“采樣-比較-調整”閉環控制LDO的關鍵參數——選型核心依據LDO與其他穩壓器的選型對比附學習參考網址歡迎大家有問題評論交流 (* ^ ω ^)變頻器實習DAY40 一、工作內容 1.1 調整…