Stream中的数据处理

接口Stream类似于一个迭代器,但提供了更为丰富的操作,Stream API的主要操作就定义在该接口中。Java 8给Collection接口增加了两个默认方法,它们可以返回一个Stream,如下所示:

default Stream<E> stream() {
	return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
	return StreamSupport.stream(spliterator(), true);
}

stream()返回的是一个顺序流,parallelStream()返回的是一个并行流。

顺序流就是由一个线程执行操作。

并行流内部会使用多线程,线程个数一般与系统的CPU核数一样,以充分利用CPU的计算能力;使用并行流不需要显式管理线程,使用方法与顺序流是一样的;并行流内部会使用Java 7引入的fork/join框架,即处理由fork和join两个阶段组成,fork就是将要处理的数据拆分为小块,多线程按小块进行并行计算,join就是将小块的计算结果进行合并。

优点

  1. 没有显式的循环迭代,循环过程被Stream的方法隐藏了。
  2. 提供了声明式的处理函数,比如filter,它封装了数据过滤的功能,而传统代码是命令式的,需要一步步的操作指令。
  3. 流畅式接口,方法调用链接在一起,清晰易读。

中间操作和终端操作

像filter和map这种不实际触发执行、用于构建流水线、返回Stream的操作称为中间操作(intermediate operation),而像collect这种触发实际执行、返回具体结果的操作称为终端操作(terminal operation)。

类型

函数

作用

说明

中间操作

filter

数据过滤

  • 无状态。

map

数据转换

distinct

数据去重

  • 是否重复是根据equals方法来比较的;
  • distinct与filter和map是不同的。filter和map都是无状态的,对于流中的每一个元素,处理都是独立的,处理后即交给流水线中的下一个操作;distinct不同,它是有状态的,在处理过程中,它需要在内部记录之前出现过的元素,如果已经出现过,即重复元素,它就会过滤掉,不传递给流水线中的下一个操作。对于顺序流,内部实现时,distinct操作会使用HashSet记录出现过的元素,如果流是有顺序的,需要保留顺序,会使用LinkedHashSet。

sorted

数据排序

  • 排序基于Comparable接口或自定义的Comparator;
  • 与distinct一样,sorted也是一个有状态的中间操作,在处理过程中,需要在内部记录出现过的元素。其不同是,每碰到流中的一个元素,distinct都能立即做出处理,要么过滤,要么马上传递给下一个操作;sorted需要先排序,为了排序,它需要先在内部数组中保存碰到的每一个元素,到流结尾时再对数组排序,然后再将排序后的元素逐个传递给流水线中的下一个操作。

skip

跳过流中的n个元素

  • 有状态;
  • 如果流中元素不足n个,返回一个空流;
  • 对前n个元素,skip的操作就是过滤,对后面的元素,skip就是传递给流水线中的下一个操作。

limit

限制流的长度

  • 有状态;
  • 它不需要处理流中的所有元素,只要处理的元素个数达到maxSize,后面的元素就不需要处理了,这种可以提前结束的操作称为短路操作。

peek

传递给自定义的consumer

  • 无状态;
  • 返回的流与之前的流是一样的,没有变化,但它提供了一个Consumer,会将流中的每一个元素传给该Consumer。

mapToLong

转为Long值

为避免装箱/拆箱,提高性能,Stream还有这几个返回基本类型特定流的方法。

mapToInt

转为Int值

mapToDouble

转为Double值

flatMap

完成了一个1到n的映射

它接受一个函数mapper,对流中的每一个元素,mapper会将该元素转换为一个流Stream,然后把新生成流的每一个元素传递给下一个操作。比如:

[hello abc, 老马, 编程] 通过空格拆分字符串后转换为 [hello, abc, 老马, 编程]

终端操作

collect

强大的收集器

实现功能:

  • 容器收集器:转为集合,比如toList,toSet,toMap,toCollection等
  • 字符串收集器:拼接成一个字符串,比如joining
  • 分组:即groupBy,比如groupingBy(该函数很强大,第二个参数可以是各种逻辑),partitioningBy。groupingBy和partitioningBy都可以接受一个下游收集器,对同一个分组或分区内的元素进行进一步收集,而下游收集器又可以是分组或分区,以构建多级分组。

max

求最大值

它们的返回值类型是Optional<T>,而不是T。

min

求最小值

count

返回元素个数

allMatch

所有都满足

  • 这几个函数都接受一个谓词Predicate,返回一个boolean值,用于判定流中的元素是否满足一定的条件;
  • 如果流为空,那么这几个函数的返回值都是true。
  • 这几个操作都是短路操作,不一定需要处理所有元素就能得出结果。比如,对于all-Match,只要有一个元素不满足条件,就能返回false。

anyMatch

任意一个满足

noneMatch

都不满足

findFirst

返回第一个

  • 都是短路操作;
  • 返回类型都是Optional,如果流为空,返回Optional.empty()。

findAny

返回任意一个

forEach

挨个遍历

  • 接受一个Consumer,对流中的每一个元素,传递元素给Consumer。

toArray

转为数组

reduce

规约(聚合为一个值)

收集器

Collector,这是一个接口,它的定义基本上是:

public interface Collector<T, A, R> {
	Supplier<A> supplier();
	BiConsumer<A, T> accumulator();
	BinaryOperator<A> combiner();
	Function<A, R> finisher();
	Set<Characteristics> characteristics();
}

在顺序流中,collect方法与这些接口方法的交互大概是这样的:

//首先调用工厂方法supplier创建一个存放处理状态的容器container,类型为A
A container = collector.supplier().get();

//对流中的每一个元素t,调用累加器accumulator,参数为累计状态container和当前元素t
for(T t : data)
	collector.accumulator().accept(container, t);
	
//最后调用finisher对累计状态container进行可能的调整,类型转换(A转换为R),返回结果
return collector.finisher().apply(container);

combiner只在并行流中有用,用于合并部分结果。

characteristics用于标示收集器的特征,Collector接口的调用者可以利用这些特征进行一些优化。

Characteristics是一个枚举,有三个值:CONCURRENT、UNORDERED和IDENTITY_FINISH。

Collectors.toList()具体是什么呢?看下代码:

public static <T>
Collector<T, ? , List<T>> toList() {
	return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
							  (left, right) ->
								  { left.addAll(right); return left; },
							  CH_ID);
}

#函数式编程##java原理#
Java知识专辑 文章被收录于专栏

知其然知其所以然,只有掌握了底层原理,借助第一性原理,才可以在日常开发和项目中运用自如,潇洒走江湖。

全部评论
看完这篇文档,来看看这个题吧:https://www.nowcoder.com/discuss/860132914233638912
点赞 回复 分享
发布于 03-08 12:22 上海

相关推荐

04-03 22:41
兰州大学 C++
老六f:有时候是HR发错了,我之前投的百度的后端开发,他给我发的算法工程师,但是确实面的就是百度开发
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务