基于本文回答
0
评论

讲讲 Java中的特化数值流(IntStream, LongStream, DoubleStream)?

知识点图片

在 Java 8 中引入的 Stream API 极大地方便了集合数据的处理。然而,Java 的泛型是基于对象的,这意味着像 Stream<Integer> 这样的流,在操作过程中会产生大量的自动装箱(Boxing)拆箱(Unboxing)开销,这在处理大量数值时会导致严重的性能问题。

为了解决这个问题,Java 引入了特化数值流(Primitive Streams):IntStreamLongStreamDoubleStream。它们分别专门用于处理原始类型 intlongdouble,从而避免了装箱/拆箱的开销。


一、 为什么需要特化数值流?

假设我们要对一个数字列表求和:

java
// 使用普通的 Stream<Integer>
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                 .reduce(0, Integer::sum); // 背后隐藏着反复的装箱和拆箱

在上面的代码中,每一次加法运算,Java 都需要把 Integer 拆箱成 int,相加后,再把结果装箱成 Integer

如果改用 IntStream

java
int sum = numbers.stream()
                 .mapToInt(Integer::intValue) // 转换为 IntStream
                 .sum(); // 直接对原始类型 int 求和,无装箱拆箱,极其高效

二、 三大特化数值流介绍

Java 只提供了针对三种最常用数值类型的特化流:

  1. IntStream:对应 int 类型。
  2. LongStream:对应 long 类型。
  3. DoubleStream:对应 double 类型。

注:对于 floatshortbytechar,通常可以使用 IntStream 来代替处理。


三、 如何创建数值流?

创建特化数值流的方法非常多样:

1. 从对象流转换(最常用)

通过 mapToIntmapToLongmapToDouble 将普通流转换为数值流。

java
List<User> users = Arrays.asList(new User("张三", 18), new User("李四", 20));
IntStream ageStream = users.stream().mapToInt(User::getAge);

2. 直接通过数值创建

java
IntStream s1 = IntStream.of(1, 2, 3, 4, 5);
DoubleStream s2 = DoubleStream.of(1.1, 2.2, 3.3);

3. 数值范围(Range)

这是 IntStreamLongStream 特有的方法,非常适合替代传统的 for 循环。

java
// range:左闭右开 [1, 100),不包含 100
IntStream range = IntStream.range(1, 100); 

// rangeClosed:双闭区间 [1, 100],包含 100
IntStream rangeClosed = IntStream.rangeClosed(1, 100); 

4. 从数组创建

java
int[] intArray = {1, 2, 3};
IntStream arrayStream = Arrays.stream(intArray);

四、 特化流的专属操作(普通流没有的)

特化数值流不仅免去了装箱开销,还提供了一些普通流不具备的便捷聚合方法

  • sum():求和。如果流为空,返回 0
  • average():求平均值。返回 OptionalDouble(因为空流没有平均值)。
  • max() / min():求最大/最小值。返回 OptionalInt / OptionalLong / OptionalDouble
  • summaryStatistics()终极武器。一次性获取统计信息(个数、总和、最大值、最小值、平均值)。

示例:神奇的 summaryStatistics

java
IntStream ages = IntStream.of(18, 20, 25, 30, 45);
IntSummaryStatistics stats = ages.summaryStatistics();

System.out.println("最大年龄: " + stats.getMax());
System.out.println("最小年龄: " + stats.getMin());
System.out.println("平均年龄: " + stats.getAverage());
System.out.println("总人数: " + stats.getCount());
System.out.println("年龄总和: " + stats.getSum());

五、 类型转换:数值流与对象流的互转

在实际开发中,我们经常需要在数值流和对象流(如 Stream<Integer>)之间来回转换。

1. 数值流 -> 对象流 (装箱)

  • 使用 boxed() 方法:
    java
    IntStream intStream = IntStream.of(1, 2, 3);
    Stream<Integer> stream = intStream.boxed(); // 转为 Stream<Integer>
  • 使用 mapToObj() 进行自定义对象包装:
    java
    IntStream.rangeClosed(1, 5)
             .mapToObj(i -> new User("User" + i, 18)) // 包装成 User 对象流
             .forEach(System.out::println);

2. 数值流之间的转换

java
IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = intStream.mapToDouble(i -> (double) i); // int 转 double

六、 性能对比(为什么它快?)

为什么数值流性能好?

  1. 内存占用极小
    • 一个 int 只占 4 字节。
    • 一个 Integer 对象在 64 位 JVM 上通常占用 16 到 24 字节,并且由于是对象,它还包含对象头信息和内存对齐开销。
  2. CPU 缓存友好
    • 原始类型的数组在内存中是连续存储的(Cache-line 友好)。
    • 对象数组(如 Integer[])存储的是引用,这些引用指向堆内存中散落的各个对象,容易导致 CPU 缓存失效(Cache Miss)。

七、 总结与最佳实践

  1. 优先使用特化流:只要处理的是基本数值类型(int, long, double),就应该毫不犹豫地选择 IntStream, LongStream, DoubleStream
  2. 替代 for-i 循环:如果你需要写一个指定次数的循环,用 IntStream.range(0, n) 会比传统的 for(int i=0; i<n; i++) 更优雅,且易于并行化(.parallel())。
  3. 注意 Optional 返回值:特化流的 min, max, average 返回的是 OptionalInt 等特化 Optional,使用时可以通过 .orElse(defaultValue) 安全地获取值。
右滑查看面试常问