Java Collection框架(二)Collection与Stream

​Java Collection框架 接口Collection 与 JDK8 集合增强 Stream 流

拍摄于京都岚山天龙寺

微信公众号

王皓的GitHub:https://github.com/TenaciousDWang

 

今天主要看JDK8中Collection接口新增的功能Stream流,关于Collection接口只是带过,相信大家对Collection接口都不陌生,这次并不展开。

 

 

JDK2的新功能,用来替换老旧的数据结构,形成一套完整的体系。

 

Collection接口

 

Collection接口继承自Iterable,其主要抽象方法包括:

 

1.添加

boolean add(Object o)添加对象到集合

boolean addAll(Collection c)将集合c中所有的元素添加给该集合

 

2.删除

void clear()删除集合中所有元素

boolean remove(Object o)删除指定的对象

boolean removeAll(Collection c)从集合中删除c集合中也有的元素

boolean retainAll(Collection c)从集合中删除集合c中不包含的元素

 

3.判断

boolean isEmpty()判断集合是否为空

boolean contains(Object o)查找集合中是否有指定的对象

boolean containsAll(Collection c)查找集合中是否有集合c中的元素

 

4.获取数量

int size()返回当前集合中元素的数量

5.获取集合元素
Iterator iterator()返回一个迭代器
Spliterator<E> spliterator()返回一个分割迭代器JDK8新增

 

6.标识

int hashCode();生成HashCode

boolean equals(Object o);避免哈希冲突

 

7.将集合变成数组

 Object[] toArray();

<T> T[] toArray(T[] a);

 

8.Stream

Stream<E> stream();流,Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。

Stream<E> parallelStream();是一个并行执行的流.它通过默认的ForkJoinPool,可能提高你的多线程任务的速度。

 

List直接继承Collection

 

 

LIst集合是线性数据结构的主要实现,集合元素通常存在明确的上一个元素或下一个元素,也存在明确第一个元素和最后一个元素。List集合的遍历结果是稳定的。最常用的如:ArrayList与LinkedList。

 

Set直接继承Collection

 

 

Set不允许出现重复元素的集合类型。常用的如HashSet、TreeSet、LinkedHashSet。

 

Queue直接继承Collection

 

 

Queue队列是一种先进先出的数据结构,是一种特殊的线性表,它只允许在表的一端进行获取,在表的另一端进行插入。没有元素时为空队列,前面在看多线程时,我们已经学习过阻塞队列,用起来非常方便。

 

顶级接口Map

 

Map名义上属于Java Collections Framework,且其实现类中也有部分依赖于Collection如keySet()查看所有key,values()查看所有value,使用entrySet()查看所有键值对,但实际上Map是一个独立的顶级接口。以Key-Value键值对作为存储元素实现的哈希结构,Key按照哈希函数计算为唯一,Value可以重复。最常用的如HashMap,线程不安全,ConcurrentHashMap,线程安全的。

 

 

以上是对这四种常用的集合类型进行简单描述,后面会展看重要的集合实现,先挖个坑。

 

Stream

 

JDK8为Collection新添加了Stream流,跟我们以往的IO流或者XML处理

中的流没有任何关系,是一个船新针对集合数据进行便捷高效的聚合操作与大批量数据处理操作。

 

聚合操作,举个秒懂例子就是报表功能,统计,排序,筛选等操作,绝大部分时间我们都是通过SQL在数据库中解决,或者在代码中进行大量遍历,比较,判断等。

 

写一个代码例子传统聚合操作。

 

 

新建一个水果Fruit对象Entity。

 

 

创建五个水果,带ID编号,名字,类型,价格,然后我们需要获取big类型的以价格降序排序的水果ID集合,以上为传统做法。

 

以下是Stream直接操作。

 

 

代码简洁,优雅,执行效率提高。

 

什么是流(摘自IBM Developer)

 

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。Stream 的另外一大特点是,数据源本身可以是无限的。

 

简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。

 

如何创建一个Stream流

 

我们首先来看一下Stream的构造创建与转换在 Java 8 中, 集合接口有两个方法来生成流:

 

  • stream() − 为集合创建串行流。
  • parallelStream() − 为集合创建并行流。

 

除了集合以外,数组也可创建,同时也可也直接创建Stream类。

 

 

Stream虽然支持Stream<Integer>、Stream<Long> 、Stream<Double>这样创建基本数据类型包装类,但是会在拆箱装箱耗费时间,所以Stream还提供了IntStream、LongStream、DoubleStream这三种常用的Stream包装类,其他的数据类型暂时还不支持。

 

 

执行结果:

 

 

如何对流进行操作

 

当我们将数据集合或者容器包装为Stream流时,我们就可以开始操作了它了,流的操作类型分为两种:

 

Intermediate:中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。

 

Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作,一去不复返。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

 

接下来看一下 Stream 的比较常用的操作。

 

Collectors

 

Collectors 类实现了很多归约操作,流转换为其它数据结构,例如将流转换成集合和聚合元素。这是一个Terminal操作。

 

 

map

 

map 方法用于映射每个元素到对应的结果,它的作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。

 

 

执行结果:

 

 

 

执行结果:

 

 

flatMap 

 

flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。

 

 

执行结果:

 

 

filter

 

方法用于通过设置的条件过滤出元素。

 

从一个数组里取偶数

 

 

执行结果:

 

 

 

执行结果:

 

 

forEach

 

forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

 

 

执行结果:

 

 

forEach 是 terminal 操作,因此它执行后,Stream 的元素就一去不复返了,如果需要 intermediate 操作可以使用具有相似功能的 peek 。

 

peek

 

 

执行结果:

 

 

peek方法作为intermediate 操作可以被多次执行,注意如果缺少terminal 操作,那么前面的intermediate 操作都不会被执行,对比上述例子缺少最后一个collect操作时,将不会有任何水果被打印到控制台。

 

reduce


 

 

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于:

Integer sum = integers.reduce(0, (a, b) -> a+b); 

Integer sum = integers.reduce(0, Integer::sum);

 

 

limit/skip

 

limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。

 

 

执行结果:

 

 

sorted

 

对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以根据需要首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。但是我在下面例子中将map放到了后面,因为如果进行map操作Stream内部元素会变为String类型而导致无法使用getId排序,编译无法通过,这就体现出了Stream短路的特点,在编译阶段提示修正可能遇到的问题。

 

 

执行结果:

 

 

min/max/distinct

 

min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

 

 

执行结果:

 

 

distinct已经在上面举过例子了。

 

 

Match

 

Stream 有三个 match 方法

 

 

allMatch:Stream 中全部元素符合传入的 predicate,返回 true。

 

anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true。

 

noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true。

 

它们都不是要遍历全部元素才能返回结果,是短路的。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。

 

 

执行结果:

 

 

自定义流

 

出了上面由JDK提供的流及流操作,我们还可以自定义流,两种方法。

 

第一种是通过实现 Supplier 接口,把 Supplier 实例传递给 Stream.generate() 生成的 Stream,可以自己来控制流的生成。

 

 

执行结果:

 

 

Stream.generate() 还接受自己实现的 Supplier。

 

 

随机生成水果。

 

 

执行结果:

 

 

用Collectors来进行groupingBy与partitioningBy操作

 

groupingBy归组类似于SQL中的group by。

 

 

执行结果:

 

 

partitioningBy根据条件。

 

 

执行结果:

 

 

怎么正确使用parallelStream

深入浅出Stream和parallelStream

摘自:https://blog.csdn.net/u011001723/article/details/52794455

(关于Stream和parallelStream推荐去看着一篇文章文章,不留坑

 

如果你正在写一个其他地方都是单线程的程序并且准确地知道什么时候你应该要使用parallel streams,这样的话你可能会觉得这个问题有一点肤浅。然而,我们很多人是在处理web应用、各种不同的框架以及重量级应用服务。一个服务器是怎样被设计成一个可以支持多种独立应用的主机的?谁知道呢,给你一个可以并行的却不能控制输入的parallel stream.

很抱歉,请原谅我用的标注[怎么正确使用parallelStream],因为目前为止我也没有发现一个好的方式来让我真正的正确使用parallelStream.下面的网上写的两种方式:

一种方式是限制ForkJoinPool提供的并行数。可以通过使用-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 来限制线程池的大小为1。不再从并行化中得到好处可以杜绝错误的使用它(其实这个方式还是有点搞笑的,既然这样搞那我还不如不去使用并行流)

另一种方式就是,一个被称为工作区的可以让ForkJoinPool平行放置的 parallelStream() 实现。不幸的是现在的JDK还没有实现。

Parallel streams 是无法预测的,而且想要正确地使用它有些棘手。几乎任何parallel streams的使用都会影响程序中无关部分的性能,而且是一种无法预测的方式。。但是在调用stream.parallel() 或者parallelStream()时候在我的代码里之前我仍然会重新审视一遍他给我的程序究竟会带来什么问题,他能有多大的提升,是否有使用他的意义.

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值