JavaStream:无限流、数组转换与文件I/O操作
立即解锁
发布时间: 2025-08-17 02:06:47 订阅数: 3 

### Java Stream:无限流、数组转换与文件 I/O 操作
#### 1. 迭代流与 for 循环
迭代流相较于 for 循环的显著优势在于,它既能实现类似循环的迭代,又能借助惰性函数式流管道带来的好处。流创建时无需定义结束条件,可由后续的中间操作(如 `limit`)或终端条件(如 `anyMatch`)提供。
迭代流的特性包括 ORDERED、IMMUTABLE,对于原始流还有 NONNULL。若迭代基于数字且范围已知,可使用 `IntStream` 和 `LongStream` 的静态范围方法创建流,以实现更多流优化,如短路操作:
- `IntStream range(int startInclusive, int endExclusive)`
- `IntStream rangeClosed(int startInclusive, int endInclusive)`
- `LongStream range(long startInclusive, long endExclusive)`
- `LongStream rangeClosed(long startInclusive, long endInclusive)`
虽然使用 `iterate` 也能实现相同结果,但主要区别在于底层的 `Spliterator`。返回流的特性有 ORDERED、SIZED、SUBSIZED、IMMUTABLE、NONNULL、DISTINCT 和 SORTED。选择迭代流还是范围流创建方式,取决于具体需求。迭代方式在迭代过程中更灵活,但会失去一些有助于优化的流特性,尤其在并行流中。
#### 2. 无限流
流的惰性特性允许处理无限元素序列,这些元素按需处理,而非一次性处理。JDK 中的所有流接口(如 `Stream<T>` 及其原始类型相关的 `IntStream`、`LongStream` 和 `DoubleStream`)都有静态便捷方法,可基于迭代或无序生成方式创建无限流。
之前提到的 `iterate` 方法从种子值开始,依赖对当前迭代值应用 `UnaryOperator`;而静态 `generate` 方法仅依赖 `Supplier` 生成下一个流元素:
- `<T> Stream<T> generate(Supplier<T> s)`
- `IntStream generate(IntSupplier s)`
- `LongStream generate(LongSupplier s)`
- `DoubleStream generate(DoubleSupplier s)`
缺少起始种子值会影响流的特性,使其变为 UNORDERED,这在并行使用时可能有益。由 `Supplier` 创建的无序流适用于常量、非相互依赖的元素序列,如随机值。例如,创建 UUID 流工厂很简单:
```java
Stream<UUID> createStream(long count) {
return Stream.generate(UUID::randomUUID)
.limit(count);
}
```
无序流的缺点是,在并行环境中,`limit` 操作不能保证选取前 n 个元素,可能导致调用元素生成 `Supplier` 的次数超过实际所需。例如:
```java
Stream.generate(new AtomicInteger()::incrementAndGet)
.parallel()
.limit(1_000L)
.mapToInt(Integer::valueOf)
.max()
.ifPresent(System.out::println);
```
此管道的预期输出为 1000,但实际输出很可能大于 1000。这表明在并行执行环境中,无序流会出现这种情况。多数情况下影响不大,但凸显了选择合适流类型以获得最佳性能和最少调用次数的必要性。
#### 3. 随机数
流 API 对生成无限随机数流有特殊考虑。虽然可以使用 `Stream.generate` 和 `java.util.Random` 实例创建此类流,但有更简单的方法。三种随机数生成类型可创建流:
- `java.util.Random`
- `java.util.concurrent.ThreadLocalRandom`
- `java.util.SplittableRandom`
这三种类型都提供了多种方法来创建随机元素流:
| 方法 | 描述 |
| --- | --- |
| `IntStream ints()` | 创建一个无限的 `IntStream`,包含随机整数 |
| `IntStream ints(long streamSize)` | 创建一个包含指定数量随机整数的 `IntStream` |
| `IntStream ints(int randomNumberOrigin, int randomNumberBound)` | 创建一个无限的 `IntStream`,包含指定范围内的随机整数 |
| `IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound)` | 创建一个包含指定数量、指定范围内随机整数的 `IntStream` |
| `LongStream longs()` | 创建一个无限的 `LongStream`,包含随机长整数 |
| `LongStream longs(long streamSize)` | 创建一个包含指定数量随机长整数的 `LongStream` |
| `LongStream longs(long randomNumberOrigin, long randomNumberBound)` | 创建一个无限的 `LongStream`,包含指定范围内的随机长整数 |
| `LongStream longs(long streamSize, long randomNumberOrigin, long randomNumberBound)` | 创建一个包含指定数量、指定范围内随机长整数的 `LongStream` |
| `DoubleStream doubles()` | 创建一个无限的 `DoubleStream`,包含随机双精度浮点数 |
| `DoubleStream doubles(long streamSize)` | 创建一个包含指定数量随机双精度浮点数的 `DoubleStream` |
| `DoubleStream doubles(double randomNumberOrigin, double randomNumberBound)` | 创建一个无限的 `DoubleStream`,包含指定范围内的随机双精度浮点数 |
| `DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound)` | 创建一个包含指定数量、指定范围内随机双精度浮点数的 `DoubleStream` |
从技术上讲,这些流实际上是有效的无限流。若未提供 `streamSize`,生成的流将包含 `Long.MAX_VALUE` 个元素。上下界由 `randomNumberOrigin`(包含)和 `randomNumberBound`(不包含)设置。
#### 4. 内存限制
使用无限流时,要牢记内存是有限的。限制无限流不仅重要,而且是绝对必要的!若忘记添加限制中间或终端操作,必然会耗尽 JVM 可用的所有内存,最终抛出 `OutOfMemoryError`。
可用于限制任何流的操作如下表所示:
| 操作 | 描述 |
| --- | --- |
| **中间操作** | |
| `limit(long maxSize)` | 将流限制为最多 `maxSize` 个元素 |
| `takeWhile(Predicate<T> predicate)` | (Java 9+)获取元素,直到谓词评估为 false |
| **终端操作(保证终止)** | |
| `Optional<T> findFirst()` | 返回流的第一个元素 |
| `Optional<T> findAny()` | 返回一个单一的、非确定性的流元素 |
| **终端操作(不保证终止)** | |
| `boolean anyMatch(Predicate<T> predicate)` | 返回流中是否有任何元素匹配谓词 |
| `boolean allMatch(Predicate<T> predicate)` | 返回流中所有元素是否都匹配谓词 |
| `boolean noneMatch(Predicate<T> predicate)` | 返回流中是否没有元素匹配谓词 |
最直接的选择是 `limit`。使用谓词的基于选择的操作(如 `takeWhile`)需谨慎编写,否则可能导致流消耗比所需更多的内存。对于终端操作,只有 `find-` 操作能保证终止流。`-Match` 操作与 `takeWhile` 有相同问题,若谓词不匹配,流管道将处理无限数量的元素,耗尽所有可用内存。
限制操作在流中的位置也会影响通过的元素数量。尽早限制流元素的流动,即使最终结果相同,也能节省更多内存和 CPU 周期。
#### 5. 数组与流的转换
数组是一种特殊的对象,类似于集合结构,持有其基本类型的元素。除了从 `java.lang.Object` 继承的常用方法外,它仅提供按索引访问特定元素和获取数组总长度的方法。在未来 Project Valhalla 可用之前,数组是存储原始类型集合的唯一方式。
数组的两个特性使其适合基于流的处理:一是创建时长度固定,不会改变;二是元素有序。因此,`java.util.Arrays` 提供了多个便捷方法,可根据不同基本类型创建合适的流。从流创建数组则通过合适的终端操作完成。
##### 5.1 对象类型数组
创建典型的 `Stream<T>` 可使用 `java.util.Arrays` 的两个静态便捷方法:
- `<T> Stream<T> stream(T[] array)`
- `<T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)`
从流转换回数组可使用以下两个终端操作:
- `Object[] toArray()`
- `<A> A[] toArray(IntFunction<A[]> generator)`
第一个变体无论流的实际元素类型如何,只能返回 `Object[]` 数组,因为 JV
0
0
复制全文
相关推荐










