使用Stream API在内存中处理数据

使用Stream API在内存中处理数据

介绍Map-Filter-Reduce(映射-过滤-归约)算法

这是非常经典的处理数据的算法。下面是一个销售类

public class Sale {
    private String product;
    private LocalDate date;
    private int amount;

    // constructors, getters, setters
    // equals, hashCode, toString
}

假设需要计算出三月分销售的总价

List<Sale> sales = ...; // this is the list of all the sales
int amountSoldInMarch = 0;
for (Sale sale: sales) {
    if (sale.getDate().getMonth() == Month.MARCH) {
        amountSoldInMarch += sale.getAmount();
    }
}
System.out.println("Amount sold in March: " + amountSoldInMarch);
  1. 过滤出3月份的销售
  2. 提取销售类中的amount属性,即映射Saleamount
  3. 求销售总额,即将各个销售额归约为总销售额

类似SQL语言

select sum(amount)
from Sales
where extract(month from date) = 3;

指定结果代替编程式算法(命令式编程vs声明式编程)

Stream API 的两个目标是使您能够创建更具可读性和表现力的代码,并为 Java 运行时提供一些摆动空间来优化您的计算。

将对象映射到其他对象或值

Map-Filter-Reduce(映射-过滤-归约)算法中映射,映射是一对一的转换:如果您映射一个包含 10 个对象的列表,您将得到一个包含 10 个转换对象的列表。

映射可以改变对象类型,但不改变对象的顺序

映射由Function函数式接口建模。

过滤掉对象

过滤并不能触及改变对象,仅仅决定是否选择或者删除对象。

过滤可以改变对象的顺序,但不能改变对象的类型

过滤由Predicate函数式接口建模。

由对象归约产生结果

归约类似于SQL中的聚合。类如COUNT, SUM, MIN, MAX, AVERAGE。Stream API 支持所有这些聚合。

归约允许通过你的数据构建出复杂的数据结构,包括List, Set, Map或者你自己构建的结构。

优化 Map-Filter-Reduce 算法

统计超过100k人口城市的总人口

不使用Stream API

List<City> cities = ...;

int sum = 0;
for (City city: cities) {
    int population = city.getPopulation();
    if (population > 100_000) {
        sum += population;
    }
}

System.out.println("Sum = " + sum);

使用伪代码

int sum = cities.map(city -> city.getPopulation())
                .filter(population -> population > 100_000)
                .sum();

考虑伪代码的返回值

Collection<Integer> populations         = cities.map(city -> city.getPopulation());
Collection<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum                                 = filteredPopulations.sum();

链式调用可读性更高,但上面伪代码也是正确的。

分析上面的代码:

  • 第一步映射,如果你想处理1000个City集合,则处理提取City其中population属性映射到1000个population属性集合中。
  • 第二部过滤,过滤掉集合中100k人口以下的population属性。

存储中间集合结果会有大量的内存开销,特别是当要处理的数据集合很大的时候。使用循环编码的话就没有这方面的开销,它将结果直接累加并不存储在中间集合中。

为避免这种开销,正确的模式:

Stream<City> streamOfCities         = cities.stream();
Stream<Integer> populations         = streamOfCities.map(city -> city.getPopulation());
Stream<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum = filteredPopulations.sum(); // in fact this code does not compile; we'll fix it later

Stream接口避免了创建中间结构用于存储映射、过滤的结果。map()filter()方法依旧返回新的Stream对象。

Stream对象不存储任何数据

Stream API 的设计方式是,只要您不在stream流模式中创建任何非Stream对象,就不会对您的数据进行计算。

stream流处理操作如同上面循环操作一样,没有额外的内存消耗。

使用stream流是关于创建操作pipeline管道。在某些时候,您的数据将通过此管道传输并被转换、过滤,然后将参与结果的产生。

pipeline管道由stream流上的一系列方法调用组成。每个调用都会产生另一个stream流。然后在某个时候,最后一次调用会产生结果。返回另一个stream流的操作称为中间操作。另一方面,返回其他内容(包括 void)的操作称为终端操作。

使用中间操作创建pipeline管道

中间操作是返回另一个流的操作。调用这样的操作会在现有的操作管道上增加一项操作,而无需处理任何数据。它由返回stream流的方法建模。

使用终端操作计算结果

终端操作是不返回stream流的操作。调用这样的操作会触发stream流源元素的消耗。然后这些元素由中间操作的pipeline管道处理,一次一个元素。

终端操作由一个方法建模,该方法返回除stream流以外的任何内容,包括 void。

您不能在stream流上调用多个终端方法。如果这样做,您将收到带有以下消息的 IllegalStateException:“流已被操作或关闭”。

使用专门的数字Stream流避免装箱

Stream API 为您提供了四个接口。

  1. Stream
  2. IntStream
  3. LongStream
  4. DoubleStream

特殊的终端操作

  • sum()
  • min(), max()
  • average()
  • summaryStatistics()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值