什么是 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类型的对象,允许多个操作链式调用
终端操作:能访问数据源,不允许多个操作链式调用, 获取最终的结果数据。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。