什麼是 Stream?
Stream(流)是一個來自資料來源的元素佇列並支援聚合操作
元素佇列 資料是以一系列元素的形式存在的,按照某種順序排列,形成一個佇列。在流的概念中,這些元素通常是連續到達的,可以逐個處理,而不必一次性載入整個資料集到記憶體中。
資料來源 流的來源。 可以是集合,陣列,I/O channel, 產生器generator 等。
聚合操作 對一系列元素執行計算以生成單個彙總值的過程。例如,計算流中所有元素的平均值、總和、最大值、最小值等。
一、Stream的特點
- 無儲存:Stream本身不儲存資料,它更像是一個高階迭代器,透過一系列流水線操作來處理資料。資料仍然儲存在原始的資料來源(如集合、陣列等)中。
- 延遲執行:Stream中的操作是惰性執行的,只有在需要結果時(即觸發終端操作時)才會實際執行。這允許透過多箇中間操作組合複雜的查詢,而無需立即計算中間結果。
- 不可變性:每次對Stream的操作都會返回一個新的Stream物件,而不會修改原始資料來源。
- 並行處理能力:Stream支援並行流(parallelStream),允許在多核處理器上並行處理資料,提升處理效能。
Demo演示
不儲存資料,不改變資料來源
@Test
void demo() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(numbers); // 輸出 [1, 2, 3, 4, 5]
// 建立一個Stream
Stream<Integer> numberStream = numbers.stream();
// 看看numberStream的內容
System.out.println(numberStream); // 輸出 java.util.stream.ReferencePipeline$Head@2ecf5915
// 原始資料來源仍然存在且未改變
System.out.println("Original numbers: " + numbers); // 輸出 [1, 2, 3, 4, 5]
// 使用Stream進行操作(例如列印)
numberStream.forEach(System.out::println); // 1 2 3 4 5
// 再次確認原始資料來源未改變
System.out.println("Original numbers after using stream: " + numbers); // 輸出 [1, 2, 3, 4, 5]
}
延時執行
@Test
void demo() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> filteredStream = numbers.stream()
.filter(n -> {
System.out.println("filter" + n); // 注意列印時間
return n > 2;
});
// 未執行終端操作執行完畢後
System.out.println("Before forEach operation");
// 觸發終端操作
filteredStream.forEach(System.out::println);
// 終端操作執行完畢後
System.out.println("After forEach operation");
}
二、建立 Stream 流
順序流和並行流
順序流
- 是一種單執行緒的流。
- 它按照資料流的順序依次處理每個元素,每個元素的處理都必須等待上一個元素的處理完成才能開始。
- 使用順序流可以保證資料處理的順序和一致性。
- 適用於處理資料量較小的情況。
並行流
- 是一種多執行緒的流。
- 它可以將資料分成多個部分並行處理,每個部分都可以在不同的執行緒中處理,從而提高處理效率。
- 使用並行流可以提高資料處理的速度,適用於處理資料量較大、處理時間較長的情況。
- 但是並行流也有一些缺點,比如執行緒之間的通訊和同步會帶來額外的開銷,而且並行流可能會影響資料的順序和一致性。
建立流
從集合建立流
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 建立一個順序流
Stream<String> parallelStream = list.parallelStream(); // 建立一個並行流
從陣列建流
int[] array = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(array); // 建立一個IntStream順序流
IntStream intParallelStream = Arrays.stream(array).parallel(); // 建立一個IntStream並行流
從值建立流
Stream<String> stream = Stream.of("a", "b", "c"); // 建立一個順序流
Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5).parallel(); // 建立一個並行流
......
note
順序流:可以使用集合類的stream()方法、Arrays類的stream()方法、Stream類的of()方法、Stream類的iterate()方法、Stream類的generate()方法、Files類的lines()方法等來獲取順序流。
並行流:可以使用集合類的parallelStream()方法、Stream類的of()方法的parallel()方法、Stream類的iterate()方法的parallel()方法、Stream類的generate()方法的parallel()方法等來獲取並行流。
三、Stream的操作型別
Stream的操作可以分為中間操作和終端操作兩類。
中間操作
返回一個新的Stream物件,允許多個操作鏈式呼叫。常見的中間操作有
- filter(Predicate<? super T> predicate):根據給定的條件過濾出符合條件的元素。
@Test
void demo() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> filteredStream = numbers.stream()
.filter(n -> n % 2 == 0);
System.out.println(filteredStream); // 輸出 java.util.stream.ReferencePipeline$2@5b5a4aed
filteredStream.forEach(System.out::println); // 輸出 2 4 6
}
- map(Function<? super T, ? extends R> mapper):將每個元素透過給定的函式對映成另一個元素。
- sorted():對Stream中的元素進行自然排序(針對實現了Comparable介面的元素)。
- distinct():去除Stream中的重複元素(依賴元素的hashCode和equals方法)。
@Test
void demo() {
List<Integer> numbers = Arrays.asList(5, 3, 8, 3, 1, 5, 9, 2, 6, 7);
List<String> sortedDistinctMappedNumbers = numbers.stream()
// 將每個整數對映為 numA 表示形式
.map(num -> num.toString() + "A")
// 去重
.distinct()
// 排序
.sorted()
// 收集結果到一個新的列表中
.collect(Collectors.toList());
// 列印結果
System.out.println(sortedDistinctMappedNumbers); // 輸出 [1A, 2A, 3A, 5A, 6A, 7A, 8A, 9A]
}
- limit(long maxSize):限制Stream中元素的數量。
- skip(long n):跳過Stream中的前n個元素。
- peek(Consumer<? super T> action):對流中的每個元素執行操作並返回一個新的流,主要用於除錯。
終端操作
觸發Stream的處理並生成結果。常見的終端操作有:
- forEach(Consumer<? super T> action):對Stream中的每個元素執行給定的操作。
- collect(Collector<? super T, A, R> collector):將Stream中的元素收集到一個新的集合中。
- reduce(BinaryOperator<T> accumulator):對Stream中的元素進行歸約操作,可以實現求和、求最大值、求最小值等操作。
@Test
void demo() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
// 使用reduce方法進行求和
.reduce(0, Integer::sum); // 初始值為0,累加器為Integer::sum
System.out.println(sum); // 輸出 15
}
- count():返回Stream中元素的數量。
- min(Comparator<? super T> comparator):返回Stream中的最小元素。
- max(Comparator<? super T> comparator):返回Stream中的最大元素。
- anyMatch(Predicate<? super T> predicate):檢查Stream中是否存在滿足給定條件的元素。
- allMatch(Predicate<? super T> predicate):檢查Stream中的所有元素是否都滿足給定條件。
- noneMatch(Predicate<? super T> predicate):檢查Stream中的所有元素是否都不滿足給定條件。
三、Stream與迭代的對比
Stream | 迭代 |
---|---|
惰性求值 | 立即求值 |
可鏈式呼叫函式 | 通常使用的是for或forEach |
宣告式的:告訴程式你想要什麼結果 | 命令式:告訴程式如何執行每一步操作 |
總結
中間操作: 返回的是一個Stream型別的物件,允許多個操作鏈式呼叫
終端操作:能訪問資料來源,不允許多個操作鏈式呼叫, 獲取最終的結果資料。