这个包主要提供元素的streams函数操作,比如对collections的map,reduce.

例如:

int sum = widgets.stream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();

本例中的widgets是Stream的源,类型为Collection,通过filter->map->reduce操作获取red widget的sum weight.

除了基本的Stream,JAVA8还专门为基础类型int,long,double提供了IntStream,LongStream,DoubleStream.

** Streams和collections的不同之处**

  1. Stream没有存储。即不是一个存储结构,而是通过管道操作从Array,IO channel等转换而来。
  2. Stream本质上是泛函。Streams上的操作不会修改源数据,比如filter操作只是将符合要求的数据放在一个新的Stream,而不是真的删除源数据。
  3. Laziness-seeking,懒查询。
  4. 大小不受限制,collections 有大小限制,streams没有。比如,limit或者findfirst等Short-circuiting操作会在有限的时间内完成无限的大小的streams操作。
  5. 不可逆的,streams中的元素只能被操作一次。就像流水一样,一去不复返。如果你希望在源数据中获取一个新的Stream,必须在源数据上重新开始。

** Stream创建**

怎样创建一个Stream呢,有下面几种方法:

  1. Collection的Stream()和paralleStream()方法。
  2. Arrays.stream(Object[])
  3. Stream 类的静态方法,比如 Stream.of(Object[]),IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator);
  4. File的lines操作, BufferedReader.lines();
  5. Files中的一些方法获取file path的stream.例如
static Stream<Path>		list(Path dir)
static Stream<Path> walk(Path start, FileVisitOption... options)

6.Random.ints()返回IntStream

7.JDK中也有很多方法产生Stream,比如

BitSet.stream(),

Pattern.splitAsStream(java.lang.CharSequence),

JarFile.stream()

java.util.stream包中的接口和类

1.接口

BaseStream<T,S extends BaseStream<T,S>>

Streams的Base interface,支持各种并行和线性聚合操作。

Collector<T,A,R>,可以将元素聚集在一个容器中,容器类型可变,比如List,Map. 是一个Stream处理完毕之后的结束操作。

DoubleStream ,处理double类型的stream

DoubleStream.Builder, 可变的builder。

IntStream,处理int类型的stream.

IntStream.Builder, 可变的builder

LongStream,处理Long类型的Stream.

LongStream.Builder,可变的builder

Stream<T> ---数据流,支持并行和线性聚合操作。

Stream.Builder<T>----Stream的可变builder.

2.类

执行Collector reduce操作的类,比如将元素聚合为collection,list等。

StreamSupport,创建和处理Stream的低级工具。

3.Enum

Collector.Characteristics---Collector的属性,可以用于优化reduce 操作。

Collector.Characteristics CONCURRENT:表示collector的result container可以被多个线程同时处理。

Collector.Characteristics UNORDERED:collection操作不保证操作元素的原有顺序

Collector.Characteristics IDENTITY_FINISH:类型转换时不会检查

*** Stream的操作和管道**

Stream操作是管道操作,操作分为中间操作和结束操作两种。整个管道操作的流程为数据源->中间操作->结束操作。

中间操作比如:Stream.filter、Stream.map,可以有0个或者多个中间操作。

结束操作,比如Sream.foreach、Stream.reduce,Stream.collect,结束操作是必须的。

中间操作返回的仍然是stream,这些操作经常是“懒惰的”。比如filter操作,并非真的对源数据进行过滤,而是重新创建一个Stream。而且当多个中间操作时,不是真的每次都去遍历源数据,而是在结束操作时,才会去遍历源数据,从而执行这些中间操作。在遍历中,操作的懒惰性表现在,并不是每次都是遍历所有的数据,比如“找出长度大于1000的第一个String”,找到之后就会返回。

结束操作的目的是将Stream转换为想要的结果或者结构。当结束操作之后,当前管道结束,不可以再重复使用。如果你仍然想操作源数据,那你必须创建一个新的Stream.

中间操作又分为无状态有状态两种。比如map和filter是无状态操作,也就是各元素间的操作是无关的,可单独进行。而distinct和sorted 是有状态操作,在操作时需要用到前元素,也就是元素之间的处理是相关的。

有状态的操作必须全部执行完毕的时候才能装入结果中。

1.并行操作

JDK的默认操作都是线性的,或者说单线程的。并行操作可以通过Collection.parallelStream()和BaseStream.parallel()实现。

例如:

int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();

检查一个Stream的执行方式可以调用 isParallel() 方法。

但并不是所有的数据都是可以使用并行操作的。要执行的操作必须是可以并行执行的,也就是多个线程操作之后,和顺序执行的结果是一致的,即线程安全。

int[] wordLength = new int[12];

Stream.of("It", "is", "your", "responsibility").parallel().forEach(s -> {
if (s.length() < 12) wordLength[s.length()]++;
});

比如上面的wordlength操作就是非线程安全的。并行操作的线程安全问题可以参考博客:

2.非干扰性

通常在结束操作之前,源数据都是可以修改的。除了一些Concurrent数据,还有iterator,spliterator操作。

下面的例子就是正确的:

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
String s = sl.collect(joining(" "));

3.无状态的行为

在streams操作中使用无状态的行为,即操作的结果不受其他状态的影响,就比如上面提高的parallel操作要考虑多线程的行为,是否有线程安全问题。

4.collect,reduce操作通常是线程安全的,可参考博客

5.顺序

对于有顺序的源数据,比如list,set,Stream操作之后仍然会按照顺序排(线性操作)。

如果是非固定顺序的数据源,则不保证其最后处理的顺序。

对于线性Streams,源的固定顺序对性能没有特别影响。

对于并行Streams,不强调顺序的话显然性能会更好。比如filter,distinct,groupingBy操作,非顺序操作的效率就更好,可参考博客

另外,如果数据本身是固定顺序的,但是开发者本身不在意是否按顺序处理,那么可以通过unordered()处理之后再操作,会提高某些有状态的并行操作和结束操作的效率。

下面穿插一些线程安全的操作例子:

List<Integer> datalist=new ArrayList<>(1000);
List<Integer> datalist_collect=new ArrayList<>();
//无状态的并行操作,即元素的操作和其他元素状态无关,线程安全
long sum = IntStream.range(0,1000).parallel().filter(s->s%2==0).count();
System.out.println("并行:"+sum);
//datalist为非线程安全的,因此属于有状态并行操作,非线程安全 IntStream.range(0,1000).parallel().forEach(data->datalist.add(data));
System.out.println("并行foreach:"+datalist.size()); //collect操作线程安全
datalist_collect=IntStream.range(0,1000).parallel().collect(ArrayList::new, (c,e)->c.add(e), (c1,c2)->c1.addAll(c2));
System.out.println("并行collect:"+datalist_collect.size()); long sum2 = IntStream.range(0,1000).filter(s->s%2==0).count();
System.out.println("串行:"+sum2); List<Integer> intlist=IntStream.range(0,10).collect(ArrayList::new, (c,e)->c.add(e), (c1,c2)->c1.addAll(c2));
System.out.println("并行数据开始:");
//并行操作时无顺序 intlist.parallelStream().map(e->e+1).forEach(System.out::println); System.out.println("串行数据开始:");
//串行操作保证顺序
intlist.stream().forEach(System.out::println);
System.out.println("串行数据结束:"); 运行结果如下-----: 并行:500
并行foreach:980
并行collect:1000
串行:500
并行数据开始:
7
8
10
6
5
4
1
9
3
2
串行数据开始:
0
1
2
3
4
5
6
7
8
9
串行数据结束

6.Reduction 操作

Reduction操作将元素通过合并操作还原为一个result,比如总数,最大值,元素存入list等。

Reduction操作的函数有很多,比如 reduce(),collect(),sum(),max(),count()等。

Reduction操作不仅看起来简洁,而且可以使用并行来实现,效率高,不存在效率安全问题,比如

int sum = 0;
for (int x : numbers) {
sum += x;
}

可以写为:

int sum = numbers.parallelStream().reduce(0, Integer::sum);

这里介绍一下reduce()的定义, 该方法的定义1为:

T reduce(T identity,
BinaryOperator<T> accumulator)

该方法使用提供的identity和accumulator来获取result.等同于下面的写法,其实identity就是计算的初始值:

T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;

但于for循环不同的是,reduce操作不局限于线性运行。但对于同一个对象T,其accumulator的运算结果必须相同。

Reduce()方法的定义2是:

Optional<T> reduce(BinaryOperator<T> accumulator)

这个操作等同于:

 boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();

Accumulator的操作和上个方法一样,详见官网:

这里主要介绍Optional返回值的作用,可以参考文章

官方文档

Reduce()定义3

<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)

等同于:

 U result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;

此方法通常用于将map和reduce操作的结合来使用。

下面介绍第一种reduction操作-----stream.collect()

collect()和reduce()不同的是,reduce是从初始值开始,计算得到一个Optional结果,可以转换为一个值。

而collect操作则是将结果放supplier中,supplier即提供一个类或者lamda表达式。

这里说明一下Supplier,该接口返回一个T类型的对象,类似工厂方法。官方文档

collect()方法定义1:

<R> R collect(Supplier<R> supplier,
BiConsumer<R,? super T> accumulator,
BiConsumer<R,R> combiner)

该方法可以对stream的元素做可变的reduction 操作,可变的reduction操作指的是这个操作的结果是一个可变的结果容器,比如ArrayList,并且操作过程中是修改结果的状态而不是替换结果中的内容。

这个方法的操作等同于:

R result = supplier.get();
for (T element : this stream)
accumulator.accept(result, element);
return result;

类似

T reduce(T identity,
BinaryOperator<T> accumulator)

Collect操作是线程安全的,并且是一个结束操作

Collect方法中的supplier用于创建一个结果容器,在并行操作中,这个方法可能会被多次调用,每次返回一个最新的值。

accumulator ---用于将一个元素放入到result container,但要求其符合结合律,互不干涉,无状态性。

combiner---用于合并两个值。必须与accumulator函数一致。

collect()的定义2

<R,A> R collect(Collector<? super T,A,R> collector)

通过一个Collector完成可变的container操作。

Collector<? super T,A,R> collector的例子有:将元素放入Collection中;用StringBuffer把String连起来;

计算元素的min,max,average或者sum。 Collectors提供了很多类似的方法。详情可以参考博客java8中Collectors的使用方法举例和Function<T,R>简介

Mutable reduction

我把这个称之为“动态还原”,一个动态还原操作会把元素集合到一个动态的结果容器里,比如Collection,StringBuilder,因为这些容器在Stream中处理元素。

举例说明:

将一个stream里面的String连接为一个String,可以这样操作:

String concatenated = strings.reduce("", String::concat)

这个操作也可以并行执行。但是这样的操作往往会损耗性能,因为会执行多次String的copy操作。更有效的方法是将集合操作的结果放入StringBuilder,StringBuilder是一个动态容器(mutable container)。

因此上面的例子修改为下面的方法,效率会更高

String[] testStrings={"a","b","c","d","e"};
String testStringBuilder=Stream.of(testStrings).parallel().collect(StringBuilder::new, StringBuilder::append,StringBuilder::append).toString();

我这里附上四种String连接的方法和测试结果,从结果测试来看,的确StringBuilder在处理已经存在的String数据时速度很快,但原来旧的for循环反而比Stream快。当然这里的数据量并不是很大,实际选择时需要自己测试,根据情况选,这篇文章的分析可以参考 https://segmentfault.com/a/1190000004171551

String[] testStrings={"a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e","a","b","c","d","e"};

四种方法由快到慢的顺序是:

方法1:

StringBuilder sumbuilder=new StringBuilder();
for(int i=0;i<testStrings.length;i++)
sumbuilder.append(testStrings[i]);

方法2:

String  sumString="";
for(int i=0;i<testStrings.length;i++)
sumString+=testStrings[i];

方法3:

String testStringBuilder=Stream.of(testStrings).collect(StringBuilder::new, StringBuilder::append,StringBuilder::append).toString();

方法4:

String concatenated = Stream.of(testStrings).reduce("", String::concat);

对于组装一个list来说,下面的方法是等效的:

方法1:

ArrayList<String> strings = stream.collect(() -> new ArrayList<>(),(c, e) -> c.add(e.toString()), (c1, c2) -> c1.addAll(c2));

方法2:

ist<String> strings = stream.map(Object::toString)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

方法3:

List<String> strings = stream.map(Object::toString)
.collect(Collectors.toList());

其中,方法3利用了Collectors的一些方法,这些在写代码的过程中非常有用。

比如,还可以利用Collectors转换map:

Collector<Employee, ?, Integer> summingSalaries
= Collectors.summingInt(Employee::getSalary);
Map<Department, Integer> salariesByDept
= employees.stream().collect(Collectors.groupingBy(Employee::getDepartment,
summingSalaries));

动态还原操作的要求满足等效性,无论操作拆分与否:

A a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
R r1 = finisher.apply(a1); // result without splitting A a2 = supplier.get();
accumulator.accept(a2, t1);
A a3 = supplier.get();
accumulator.accept(a3, t2);
R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting

最后做个总结,在你希望更换掉原有的for循环操作时,如何很在乎运行的时间或者说性能,那么使用Streams操作时,务必做相应的性能检测。

如果对时间的要求不是很高,并且需要对数据进行管道操作等,可以选择Streams方法,代码看起来简洁明了,而且操作简单,但数据量较大时,其实和for循环的性能相当。

Collectors的用法详见博客:

java8中Collectors的使用方法举例和Function<T,R>简介

Lambda表达式详解见博客Java8的lambda表达式和函数式接口

整理一下java8新特性学习过程中我任务比较好的文章

  1. java8新特性终极版

    2 .Java8 新特性之Stream----java.util.stream
  2. Java8的lambda表达式和函数式接口
  3. java8中Collectors的使用方法举例和Function<T,R>简介
  4. Java8 lambda表达式10个示例
  5. 跟上 Java 8 – 你忽略了的新特性
  6. Server-Sent Events 教程

Java8 新特性之Stream----java.util.stream的更多相关文章

  1. Java8 新特性学习 Lambda表达式 和 Stream 用法案例

    Java8 新特性学习 Lambda表达式 和 Stream 用法案例 学习参考文章: https://www.cnblogs.com/coprince/p/8692972.html 1.使用lamb ...

  2. java8新特性(六):Stream多线程并行数据处理

    转:http://blog.csdn.net/sunjin9418/article/details/53143588 将一个顺序执行的流转变成一个并发的流只要调用 parallel()方法 publi ...

  3. 乐字节-Java8新特性-接口默认方法之Stream流(下)

    接上一篇:<Java8新特性之stream>,下面继续接着讲Stream 5.流的中间操作 常见的流的中间操作,归为以下三大类:筛选和切片流操作.元素映射操作.元素排序操作: 操作 描述 ...

  4. 2020了你还不会Java8新特性?(六)Stream源码剖析

    Stream流源码详解 节前小插曲 AutoCloseable接口: 通过一个例子 举例自动关闭流的实现. public interface BaseStream<T, S extends Ba ...

  5. Java8 流式 API(`java.util.stream`)

    熟悉 ES6 的开发者,肯定对数组的一些方法不是很陌生:map.filter 等.在对一组对象进行统一操作时,利用这些方法写出来的代码比常规的迭代代码更加的简练.在 C♯ 中,有 LINQ 来实现.那 ...

  6. Java8新特性 Stream流式思想(二)

    如何获取Stream流刚开始写博客,有一些不到位的地方,还请各位论坛大佬见谅,谢谢! package cn.com.zq.demo01.Stream.test01.Stream; import org ...

  7. java8 新特性入门 stream/lambda

    Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作(aggregate operation),或者大批量数据操作 (b ...

  8. 这可能是史上最好的 Java8 新特性 Stream 流教程

    本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...

  9. java8新特性--Stream的基本介绍和使用

    什么是Stream? Stream是一个来自数据源的元素队列并可以进行聚合操作. 数据源:流的来源. 可以是集合,数组,I/O channel, 产生器generator 等 聚合操作:类似SQL语句 ...

随机推荐

  1. Java课程设计—学生成绩管理系统

    一. 团队名称.团队成员介绍(需要有照片) 团队名称:进击的712 团队成员 杨雪莹[组长] 201521123005 网络1511 林楚虹 201521123002 网络1511 董美凤 20152 ...

  2. 201521123099 《Java程序设计》第13周学习总结

    本周学习总结 书面作业 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分析返回结果有何不同?为什么会有这样的不同? 1.2 telnet bbs. ...

  3. MySql数据库第一天

    LAMP  linux apache mysql php的关系服务器软件 “服务器” 数据库 操作 mysql常用数据类型:int varchar float double bit datetime. ...

  4. Java:Object类的equals()方法 如何编写一个完美的equals()方法

    一  代码实例: package freewill.objectequals;  /** * @author freewill * @see Core Java page161 * @desc get ...

  5. TileMap Editer 编辑器工具介绍

    打开Tiled 新建地图... 新建地图面板包括三部分,分别是地图.地图大小和块大小.地图包括方向.土块层格式和渲染顺序,根据场景不同选择不同地图方向,土块层格式和渲染顺序默认即可:图块大小和块大小决 ...

  6. GCD之死锁

    GCD相当好用,但用不好就会死锁,始终要记着这样一句秘籍: 不要在串行队列放dispatch_sync.dispatch_apply 下面看几个例子 1 2 3 4 5 6 7 8 9 10 11 1 ...

  7. Select()使用否?

    David Treadwell ,Windows Socket 的一位开发者,曾经在他的一篇名为"Developing Transport-Independent Applications ...

  8. hdu4614 线段树+二分 插花

    Alice is so popular that she can receive many flowers everyday. She has N vases numbered from 0 to N ...

  9. bzoj2730(矿场搭建)

    矿场搭建,不知道为什么,莫名其妙T了在212上,额,zyh数据真的坑. bzoj200轻松跑过啊. 就是点双联通分量缩点,然后标记割点,一个块如果有>=2个割点,则不需要挖矿洞, 如果只有一割点 ...

  10. 模型组合(Model Combining)之Boosting与Gradient Boosting

    版权声明: 本文由LeftNotEasy发布于http://leftnoteasy.cnblogs.com, 本文可以被全部的转载或者部分使用,但请注明出处,如果有问题,请联系wheeleast@gm ...