流(Stream)是Java8为了实现最佳性能而引入的一个全新的概念。在过去的几年中,随着硬件的持续发展,编程方式已经发生了巨大的改变,程序的性能也随着并行处理、实时、云和其他一些编程方法的出现而得到了不断提高。

Java8中,流性能的提升是通过并行化(parallelism)惰性(Laziness)短路操作(short-circuit operations)来实现的。但它也有一个缺点,在选择流的时候需要非常小心,因为这可能会降低应用程序的性能。

下面来看看这三项支撑起流强大性能的因素吧。


并行化

流的并行化充分利用了硬件的相关功能。由于现在计算机上通常都有多个CPU核心,所以在多核系统中如果只使用一个线程则会极大地浪费系统资源。设计和编写多线程应用非常具有挑战性,并且很容易出错,因此,流存在两种实现:顺序和并行。使用并行流非常简单,无需专业知识即可轻松处理多线程问题。

在Java的流中,并行化是通过Fork-Join原理来实现的。根据Fork-Join原理,系统会将较大的任务切分成较小的子任务(称之为forking),然后并行处理这些子任务以充分利用所有可用的硬件资源,最后将结果合并起来(称之为Join)组成完整的结果。

在选择顺序和并行的时候,需要非常谨慎,因为并行并一定意味着性能会更好。

让我们来看一个例子。

StreamTest.java:

Java代码

package test;
import java.util.ArrayList;
import java.util.List;
public class StreamTest {
static List < Integer > myList = new ArrayList < > ();
public static void main(String[] args) {
for (int i = 0; i < 5000000; i++)
myList.add(i);
int result = 0;
long loopStartTime = System.currentTimeMillis();
for (int i: myList) {
if (i % 2 == 0)
result += i;
}
long loopEndTime = System.currentTimeMillis();
System.out.println(result);
System.out.println("Loop total Time = " + (loopEndTime - loopStartTime));
long streamStartTime = System.currentTimeMillis();
System.out.println(myList.stream().filter(value -> value % 2 == 0).mapToInt(Integer::intValue).sum());
long streamEndTime = System.currentTimeMillis();
System.out.println("Stream total Time = " + (streamEndTime - streamStartTime));
long parallelStreamStartTime = System.currentTimeMillis();
System.out.println(myList.parallelStream().filter(value -> value % 2 == 0).mapToInt(Integer::intValue).sum());
long parallelStreamEndTime = System.currentTimeMillis();
System.out.println("Parallel Stream total Time = " + (parallelStreamEndTime - parallelStreamStartTime));
}
}

运行结果:

代码

820084320
Loop total Time = 17
820084320
Stream total Time = 81
820084320
Parallel Stream total Time = 30

正如你所见,在这种情况下,for循环更好。因此,在没有正确的分析之前,不要用流代替for循环。在这里,我们可以看到,并行流的性能比普通流更好。

注意:结果可能会因为硬件的不同而不同。

惰性

我们知道,Java8的流有两种类型的操作,分别为中间操作(Intermediate)和最终操作(Terminal)。这两种操作分别用于处理和提供最终结果。如果最终操作不与中间操作相关联,则无法执行。

总之,中间操作只是创建另一个流,不会执行任何处理,直到最终操作被调用。一旦最终操作被调用,则开始遍历所有的流,并且相关的函数会逐一应用到流上。中间操作是惰性操作,所以,流支持惰性。

注意:对于并行流,并不会在最后逐个遍历流,而是并行处理流,并且并行度取决于机器CPU核心的个数。

考虑一下这种情况,假设我们有一个只有中间操作的流片段,而最终操作要稍后才会添加到应用中(可能需要也可能不需要,取决于用户的需求)。在这种情况下,流的中间操作将会为最终操作创建另一个流,但不会执行实际的处理。这种机制有助于提高性能。

我们来看一下有关惰性的例子:

StreamLazinessTest.java:

Java代码

package test;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamLazinessTest {
/** Employee class **/
static class Employee {
int id;
String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return this.name;
}
}
public static void main(String[] args) throws InterruptedException {
List < Employee > employees = new ArrayList < > ();
/** Creating the employee list **/
for (int i = 1; i < 10000000; i++) {
employees.add(new StreamLazinessTest.Employee(i, "name_" + i));
}
/** Only Intermediate Operations; it will just create another streams and
* will not perform any operations **/
Stream < String > employeeNameStreams = employees.parallelStream().filter(employee -> employee.id % 2 == 0)
.map(employee -> {
System.out.println("In Map - " + employee.getName());
return employee.getName();
});
/** Adding some delay to make sure nothing has happen till now **/
Thread.sleep(2000);
System.out.println("2 sec");
/** Terminal operation on the stream and it will invoke the Intermediate Operations
* filter and map **/
employeeNameStreams.collect(Collectors.toList());
}
}

运行上面的代码,你可以看到在调用最前操作之前,中间操作不会被执行。


短路行为

这是优化流处理的另一种方法。 一旦条件满足,短路操作将会终止处理过程。 有许多短路操作可供使用。 例如,anyMatch、allMatch、findFirst、findAny、limit等等。

我们来看一个例子。

StreamShortCircuitTest.java:

Java代码

package test;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamShortCircuitTest {
/** Employee class **/
static class Employee {
int id;
String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
public static void main(String[] args) throws InterruptedException {
List < Employee > employees = new ArrayList < > ();
for (int i = 1; i < 10000000; i++) {
employees.add(new StreamShortCircuitTest.Employee(i, "name_" + i));
}
/** Only Intermediate Operations; it will just create another streams and
* will not perform any operations **/
Stream < String > employeeNameStreams = employees.stream().filter(e -> e.getId() % 2 == 0)
.map(employee -> {
System.out.println("In Map - " + employee.getName());
return employee.getName();
});
long streamStartTime = System.currentTimeMillis();
/** Terminal operation with short-circuit operation: limit **/
employeeNameStreams.limit(100).collect(Collectors.toList());
System.out.println(System.currentTimeMillis() - streamStartTime);
}
}

运行上面的代码,你会看到性能得到了极大地提升,在我的机器上只需要6毫秒的时间。 在这里,limit()方法在满足条件的时候会中断运行。

最后要注意的是,根据状态的不同有两种类型的中间操作:有状态(Stateful)和无状态(Stateless)中间操作。


有状态中间操作

这些中间操作需要存储状态,因此可能会导致应用程序的性能下降,例如,distinct()、sort()、limit()等等。

无状态中间操作

这些中间操作可以独立处理,因为它们不需要保存状态,例如, filter(),map()等。

在这里,我们了解到,流的出现是为了获得更高的性能,但并不是说使用了流之后性能肯定会得到提升,因此,我们需要谨慎使用。


原文地址:

http://www.iteye.com/news/32764

Java8中流的性能的更多相关文章

  1. Java8 Stream新特性详解及实战

    Java8 Stream新特性详解及实战 背景介绍 在阅读Spring Boot源代码时,发现Java 8的新特性已经被广泛使用,如果再不学习Java8的新特性并灵活应用,你可能真的要out了.为此, ...

  2. Java 8 的 JVM 有多快?Fork-Join 性能基准测试

    Java 8 已经发布一段时间了,许多开发者已经开始使用 Java 8.本文也将讨论最新发布在 JDK 中的并发功能更新.事实上,JDK 中已经有多处java.util.concurrent 改动,但 ...

  3. 初探Java8中的HashMap(转)

    HashMap是我们最常用的集合之一,同时Java8也提升了HashMap的性能.本着学习的原则,在这探讨一下HashMap. 原理 简单讲解下HashMap的原理:HashMap基于Hash算法,我 ...

  4. java8 for ,forEach ,lambda forEach , strean forEach , parller stream forEach, Iterator性能对比

    java8 for ,forEach ,Iterator,lambda forEach ,lambda  strean forEach , lambda parller stream forEach性 ...

  5. 什么是hashMap,初始长度,高并发死锁,java8 hashMap做的性能提升

    问题1:HashM安排的初始长度,为什么? 初始长度是 16,每次扩展或者是手动初始化,长度必须是 2的幂. 因为: index = HashCode(Key) & (length - 1), ...

  6. Java8 Stream性能如何及评测工具推荐

    作为技术人员,学习新知识是基本功课.有些知识是不得不学,有些知识是学了之后如虎添翼,Java8的Stream就是兼具两者的知识.不学看不懂,学了写起代码来如虎添翼. 在上篇<Java8 Stre ...

  7. java集合之hashMap,初始长度,高并发死锁,java8 hashMap做的性能提升

    众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry.这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干. HashMap ...

  8. Java8的Stream API确实很牛,但性能究竟如何?

    Stream Performance 已经对 Stream API 的用法鼓吹够多了,用起简洁直观,但性能到底怎么样呢?会不会有很高的性能损失?本节我们对 Stream API 的性能一探究竟. 为保 ...

  9. java8 如何优化CAS的性能

    场景引入 经常都会有下面这段代码,多个线程同时修改一个变量,造成线程不安全,代码如下: public class ThreadCASDemo implements Runnable { static ...

随机推荐

  1. 深入理解CSS盒模型【转载】

    下面本文章将会从以下几个方面谈谈盒模型. 基本概念:标准模型 和IE模型 CSS如何设置这两种模型 JS如何设置获取盒模型对应的宽和高 实例题(根据盒模型解释边距重叠) BFC(边距重叠解决方案) 基 ...

  2. LINUX五中IO模型

    阻塞IO模型 用户空间调用recvfrom命令 直到数据包到达且被复制到应用进程的缓冲区或发生错误时才返回,这个过程中 进程亦或线程一直处于等待阻塞状态. 2.非阻塞IO模型 用户空间调用内核指令re ...

  3. mysql基础之double,float长度标度定义

    MySQL类型float double decimal的区别 float数值类型用于表示单精度浮点数值,而double数值类型用于表示双精度浮点数值,float和double都是浮点型,而decima ...

  4. web.xml中配置启动时加载的servlet,load-on-starup

    web.xml中配置启动时加载的servlet,load-on-starup 使用servlet来初始化配置文件数据: 在servlet的配置当中,<load-on-startup>1&l ...

  5. C#扫盲篇(二)依赖倒置•控制反转•依赖注入•面向接口编程--满腹经纶的说

    扫盲系列的文章收到了广大粉丝朋友的支持,十分感谢,你们的支持就是我最大动力. 我的扫盲系列还会继续输出,本人也是一线码农,有什么问题大家可以一起讨论.也可以私信或者留言您想要了解的知识点,我们一起进步 ...

  6. Typecho 主题更换

    Typecho 主题更换 前言 上一篇已经搭建自己的 Typecho 博客,博客搭建完成自带一个默认主题,不是很喜欢默认的主题,想换一个自己喜欢的主题,并在基础上进行修改. 本文就介绍下如何更换和自定 ...

  7. Linux LVM Logical Volume Management 逻辑卷的管理

    博主是一个数据库DBA,但是一般来说,是不做linux服务器LVM 逻辑卷的创建.扩容和减容操作的,基本上有系统管理员操作,一是各司其职,专业的事专业的人做,二是做多了你的责任也多了,哈哈! 但是li ...

  8. 深入浅出Dotnet Core的项目结构变化

    有时候,越是基础的东西,越是有人不明白.   前几天Review一个项目的代码,发现非常基础的内容,也会有人理解出错. 今天,就着这个点,写一下Dotnet Core的主要类型的项目结构,以及之间的转 ...

  9. 一网打尽,一文讲通虚拟机VirtualBox及Linux使用

    本文将从虚拟机的选择.安装.Linux系统安装.SSH客户端工具使用四个方面来详细介绍Linux系统在虚拟机下的安装及使用方法,为你在虚拟机下正常使用Linux保驾护航. 1.虚拟机的选择 在讲虚拟机 ...

  10. LeetCode-151-中等-翻转字符串里面的单词

    问题描述 给定一个字符串,逐个翻转字符串中的每个单词. 说明: 无空格字符构成一个 单词 . 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括. 如果两个单词间有多余的空格,将反转 ...