欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识

深入领略J 昆山软件开发 ava Stream流水线

点击: 次  来源:宝鼎软件 时间:2017-06-02

原文出处: CarpenterLee

前面我们已经学会如何利用Stream API,用起来真的很爽,但简捷的要领下面好像埋没着无尽的奥秘,如此强大的API是如何实现的呢?Pipeline是怎么执行的,每次要领挪用城市导致一次迭代吗?自动并行又是怎么做到的,线程个数是几多?本节我们进修Stream流水线的道理,这是Stream实现的要害地址。

首先回首一下容器执行Lambda表达式的方法,以ArrayList.forEach()要领为例,详细代码如下:

// ArrayList.forEach()
public void forEach(Consumer<? super E> action) {
    ...
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);// 回调要领
    }
    ...
}

我们看到ArrayList.forEach()要领的主要逻辑就是一个for轮回,在该for轮回里不绝挪用action.accept()回调要领完成对元素的遍历。这完全没有什么新奇之处,回调要领在Java GUI的监听器中遍及利用。Lambda表达式的浸染就是相当于一个回调要领,这很好领略。

Stream API中大量利用Lambda表达式作为回调要领,但这并不是要害。领略Stream我们更体贴的是别的两个问题:流水线和自动并行。利用Stream或者很容易写入如下形式的代码:

int longestStringLengthStartingWithA
        = strings.stream()
              .filter(s -> s.startsWith("A"))
              .mapToInt(String::length)
              .max();

上述代码求出以字母A开头的字符串的最大长度,一种直白的方法是为每一次函数挪用都执一次迭代,这样做可以或许实现成果,但效率上必定是无法接管的。类库的实现着利用流水线(Pipeline)的方法巧妙的制止了多次迭代,其根基思想是在一次迭代中尽大概多的执行用户指定的操纵。为讲授利便我们汇总了Stream的所有操纵。

Stream操纵分类
中间操纵(Intermediate operations) 无状态(Stateless) unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
有状态(Stateful) distinct() sorted() sorted() limit() skip()
竣事操纵(Terminal operations) 非短路操纵 forEach() forEachOrdered() toArray() reduce() collect() max() min() count()
短路操纵(short-circuiting) anyMatch() allMatch() noneMatch() findFirst() findAny()

Stream上的所有操纵分为两类:中间操纵和竣事操纵,中间操纵只是一种标志,只有竣事操纵才会触发实际计较。中间操纵又可以分为无状态的(Stateless)和有状态的(Stateful),无状态中间操纵是指元素的处理惩罚不受前面元素的影响,而有状态的中间操纵必需比及所有元素处理惩罚之后才知道最终功效,好比排序是有状态操纵,在读取所有元素之前并不能确定排序功效;竣事操纵又可以分为短路操纵和非短路操纵,短路操纵是指不消处理惩罚全部元素就可以返回功效,好比找到第一个满意条件的元素。之所以要举办如此风雅的分别,是因为底层对每一种环境的处理惩罚方法差异。

一种直白的实现方法

Stream_pipeline_naive

仍然思量上述求最长字符串的措施,一种直白的流水线实现方法是为每一次函数挪用都执一次迭代,并将处理惩罚中间功效放到某种数据布局中(好比数组,容器等)。详细说来,就是挪用filter()要领后当即执行,选出所有以A开头的字符串并放到一个列表list1中,之后让list1通报给mapToInt()要领并当即执行,生成的功效放到list2中,最后遍历list2找出最大的数字作为最终功效。措施的执行流程如如所示:

这样做实现起来很是简朴直观,但有两个明明的漏洞:

  1. 迭代次数多。迭代次数跟函数挪用的次数相等。
  2. 频繁发生中间功效。每次函数挪用都发生一次中间功效,存储开销无法接管。

这些漏洞使得效率底下,基础无法接管。假如不利用Stream API我们都知道上述代码该如安在一次迭代中完成,大抵是如下形式:

int longest = 0;
for(String str : strings){
    if(str.startsWith("A")){// 1. filter(), 保存以A开头的字符串
        int len = str.length();// 2. mapToInt(), 转换生长度
        longest = Math.max(len, longest);// 3. max(), 保存最长的长度
    }
}