# Stream API 下
## Collector 收集
收集器用来将经过筛选、映射的流进行最后的整理,可以使得最后的结果以不同的形式展现。
`collect` 方法即为收集器,它接收 `Collector` 接口的实现作为具体收集器的收集方法。
`Collector` 接口提供了很多默认实现的方法,我们可以直接使用它们格式化流的结果;也可以自定义 `Collector` 接口的实现,从而定制自己的收集器。
### 归约
流由一个个元素组成,归约就是将一个个元素“折叠”成一个值,如求和、求最值、求平均值都是归约操作。
### 一般性归约
若你需要自定义一个归约操作,那么需要使用 `Collectors.reducing` 函数,该函数接收三个参数:
* 第一个参数为归约的初始值
* 第二个参数为归约操作进行的字段
* 第三个参数为归约操作的过程
## 汇总
Collectors类专门为汇总提供了一个工厂方法:`Collectors.summingInt`。
它可接受一 个把对象映射为求和所需int的函数,并返回一个收集器;该收集器在传递给普通的 `collect` 方法后即执行我们需要的汇总操作。
### 分组
数据分组是一种更自然的分割数据操作,分组就是将流中的元素按照指定类别进行划分,类似于SQL语句中的 `GROUPBY`。
### 多级分组
多级分组可以支持在完成一次分组后,分别对每个小组再进行分组。
使用具有两个参数的 `groupingBy` 重载方法即可实现多级分组。
* 第一个参数:一级分组的条件
* 第二个参数:一个新的 `groupingBy` 函数,该函数包含二级分组的条件
**Collectors 类的静态工厂方法**
| 工厂方法 | 返回类型 | 用途 | 示例 |
|:-----:|:--------|:-------|:-------|
| `toList` | `List<T>` | 把流中所有项目收集到一个 List | `List<Project> projects = projectStream.collect(toList());` |
| `toSet` | `Set<T>` | 把流中所有项目收集到一个 Set,删除重复项 | `Set<Project> projects = projectStream.collect(toSet());` |
| `toCollection` | `Collection<T>` | 把流中所有项目收集到给定的供应源创建的集合 | `Collection<Project> projects = projectStream.collect(toCollection(), ArrayList::new);` |
| `counting` | `Long` | 计算流中元素的个数 | `long howManyProjects = projectStream.collect(counting());` |
| `summingInt` | `Integer` | 对流中项目的一个整数属性求和 | `int totalStars = projectStream.collect(summingInt(Project::getStars));` |
| `averagingInt` | `Double` | 计算流中项目 Integer 属性的平均值 | `double avgStars = projectStream.collect(averagingInt(Project::getStars));` |
| `summarizingInt` | `IntSummaryStatistics` | 收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值 | `IntSummaryStatistics projectStatistics = projectStream.collect(summarizingInt(Project::getStars));` |
| `joining` | `String` | 连接对流中每个项目调用 toString 方法所生成的字符串 | `String shortProject = projectStream.map(Project::getName).collect(joining(", "));` |
| `maxBy` | `Optional<T>` | 按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty() | `Optional<Project> fattest = projectStream.collect(maxBy(comparingInt(Project::getStars)));` |
| `minBy` | `Optional<T>` | 按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty() | `Optional<Project> fattest = projectStream.collect(minBy(comparingInt(Project::getStars)));` |
| `reducing` | 归约操作产生的类型 | 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值 | `int totalStars = projectStream.collect(reducing(0, Project::getStars, Integer::sum));` |
| `collectingAndThen` | 转换函数返回的类型 | 包含另一个收集器,对其结果应用转换函数 | `int howManyProjects = projectStream.collect(collectingAndThen(toList(), List::size));` |
| `groupingBy` | `Map<K, List<T>>` | 根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键 | `Map<String,List<Project>> projectByLanguage = projectStream.collect(groupingBy(Project::getLanguage));` |
| `partitioningBy` | `Map<Boolean,List<T>>` | 根据对流中每个项目应用断言的结果来对项目进行分区 | `Map<Boolean,List<Project>> vegetarianDishes = projectStream.collect(partitioningBy(Project::isVegetarian));` |
### 转换类型
有一些收集器可以生成其他集合。比如前面已经见过的 `toList`,生成了 `java.util.List` 类的实例。
还有 `toSet` 和 `toCollection`,分别生成 `Set` 和 `Collection` 类的实例。
到目前为止, 我已经讲了很多流上的链式操作,但总有一些时候,需要最终生成一个集合——比如:
- 已有代码是为集合编写的,因此需要将流转换成集合传入;
- 在集合上进行一系列链式操作后,最终希望生成一个值;
- 写单元测试时,需要对某个具体的集合做断言。
使用 `toCollection`,用定制的集合收集元素
```java
stream.collect(toCollection(TreeSet::new));
```
还可以利用收集器让流生成一个值。 `maxBy` 和 `minBy` 允许用户按某种特定的顺序生成一个值。
### 数据分区
分区是分组的特殊情况:由一个断言(返回一个布尔值的函数)作为分类函数,它称分区函数。
分区函数返回一个布尔值,这意味着得到的分组 `Map` 的键类型是 `Boolean`,于是它最多可以分为两组: true是一组,false是一组。
分区的好处在于保留了分区函数返回true或false的两套流元素列表。
### 并行流
并行流就是一个把内容分成多个数据块,并用不不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。
将一个顺序执行的流转变成一个并发的流只要调用 `parallel()` 方法
```java
public static long parallelSum(long n){
return Stream.iterate(1L, i -> i +1).limit(n).parallel().reduce(0L,Long::sum);
}
```
将一个并发流转成顺序的流只要调用 `sequential()` 方法
```java
stream.parallel().filter(...).sequential().map(...).parallel().reduce();
```
这两个方法可以多次调用,只有最后一个调用决定这个流是顺序的还是并发的。
并发流使用的默认线程数等于你机器的处理器核心数。
通过这个方法可以修改这个值,这是全局属性。
```java
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");
```
并非使用多线程并行流处理数据的性能一定高于单线程顺序流的性能,因为性能受到多种因素的影响。
如何高效使用并发流的一些建议:
1. 如果不确定, 就自己测试。
2. 尽量使用基本类型的流 IntStream, LongStream, DoubleStream
3. 有些操作使用并发流的性能会比顺序流的性能更差,比如limit,findFirst,依赖元素顺序的操作在并发流中是极其消耗性能的。findAny的性能就会好很多,应为不依赖顺序。
4. 考虑流中计算的性能(Q)和操作的性能(N)的对比, Q表示单个处理所需的时间,N表示需要处理的数量,如果Q的值越大, 使用并发流的性能就会越高。
5. 数据量不大时使用并发流,性能得不到提升。
6. 考虑数据结构:并发流需要对数据进行分解,不同的数据结构被分解的性能时不一样的。
**流的数据源和可分解性**
| 源 | 可分解性 |
|:-----:|:-------|
| `ArrayList` | 非常好 |
| `LinkedList` | 差 |
| `IntStream.range` | 非常好 |
| `Stream.iterate` | 差 |
| `HashSet` | 好 |
| `TreeSet` | 好 |
**流的特性以及中间操作对流的修改都会对数据对分解性能造成影响。 比如固定大小的流在任务分解的时候就可以平均分配,但是如果有filter操作,那么流就不能预先知道在这个操作后还会剩余多少元素。**
**考虑终端操作的性能:如果终端操作在合并并发流的计算结果时的性能消耗太大,那么使用并发流提升的性能就会得不偿失。**
- 第零章 序
- 序言
- 系统架构
- 视频公开课
- 开源版介绍
- 商业版介绍
- 功能对比
- 答疑流程
- 第一章 快速开始
- 升级必看
- 环境要求
- 环境准备
- 基础环境安装
- Docker安装基础服务
- Nacos安装
- Sentinel安装
- 插件安装
- 建数据库
- 工程导入
- 导入Cloud版本
- 导入Nacos配置
- 导入Boot版本
- 工程运行
- 运行Cloud版本
- 运行Boot版本
- 工程测试
- 测试Cloud版本
- 测试Boot版本
- 第二章 技术基础
- Java
- Lambda
- Lambda 受检异常处理
- Stream 简介
- Stream API 一览
- Stream API (上)
- Stream API (下)
- Optional 干掉空指针
- 函数式接口
- 新的日期 API
- Lombok
- SpringMVC
- Swagger
- Mybatis
- Mybatis-Plus
- 开发规范
- 第三章 开发初探
- 新建微服务工程
- 第一个API
- API鉴权
- API响应结果
- Redis缓存
- 第一个CRUD
- 建表
- 建Entity
- 建Service和Mapper
- 新增 API
- 修改 API
- 删除 API
- 查询 API
- 单条数据
- 多条数据
- 分页
- 微服务远程调用
- 声明式服务调用 Feign
- 熔断机制 Hystrix
- 第四章 开发进阶
- 聚合文档
- 鉴权配置
- 跨域处理
- Xss防注入
- 自定义启动器
- Secure安全框架
- Token认证简介
- Token认证配置
- PreAuth注解配置
- Token认证实战
- Token认证加密
- 日志系统
- 原理解析
- 功能调用
- Seata分布式事务
- 简介
- 编译包启动
- 配置nacos对接
- docker启动
- 对接微服务
- 代码生成配置
- 前言
- 数据库建表
- 代码生成
- 前端配置
- 优化效果
- 第五章 功能特性
- SaaS多租户
- 概念
- 数据隔离配置
- 线程环境自定义租户ID
- 多终端令牌认证
- 概念
- 系统升级
- 使用
- 第三方系统登录
- 概念说明
- 对接说明
- 对接准备
- 配置说明
- 操作流程
- 后记
- UReport2报表
- 报表简介
- 对接配置
- 报表后记
- 动态数据权限
- 数据权限简介
- 数据权限开发
- 纯注解配置
- Web全自动配置
- 注解半自动配置
- 数据权限注意点
- 动态接口权限
- 乐观锁配置
- 统一服务登陆配置
- Skywalking追踪监控
- Minio分布式对象存储
- Boot版本对接至Cloud
- 第六章 生产部署
- windows部署
- linux部署
- jar部署
- docker部署
- java环境安装
- mysql安装
- docker安装
- docker-compose安装
- harbor安装
- 部署步骤
- 宝塔部署
- 准备工作
- 安装工作
- 部署准备
- 部署后端
- 部署前端
- 部署域名
- 结束工作
- k8s平台部署
- 第七章 版本控制
- Git远程分支合并
- Git地址更换
- 第八章 学习资料
- 第九章 FAQ
- 第十章 联系我们