摘要:Stream是jdk1.8给我们提供的新特性

本文分享自华为云社区《深入理解Stream之原理剖析》,作者: 李哥技术 。

Stream是jdk1.8给我们提供的新特性,主要就是允许我们采用声明式的方式处理数据集合,我们要知道在项目中我们集合就是我们最常用的数据存储结构,有时后我们需要对集合内的元素做一些过滤或者其他的操作我们一般是采用for循环的方式。

Stream操作分类

Stream中的操作可以分为两大类:中间操作与结束操作。

中间操作只会进行操作记录,只有结束操作才会触发实际的计算,可以理解为懒加载,这也是Stream在操作大对象迭代计算的时候如此高效的原因之一。

中间操作分为有状态操作与无状态操作,无状态是指元素的处理不受之前元素的影响,有状态是指该操作只有拿到所有元素之后才能继续下去。这也比较好理解,比如有状态的distinct()去重方法,你说他能不关心其他值吗?当然不能,他必须拿到所有元素才知道当前迭代的元素是否被重复。

结束操作可以分为短路与非短路操作,这个应该很好理解,短路是指遇到某些符合条件的元素就可以得到最终结果;而非短路是指必须处理所有元素才能得到最终结果。

之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。

Stream结构分析

让我们先简单看看下面一段代码:

 List<String> list = new ArrayList<>();
// 获取stream1
Stream<String> stream1 = list.stream();
// stream1通过filter后得到stream2
Stream<String> stream2 = stream1.filter("lige"::equals);
// stream1与stream2是同一个对象吗?
System.out.println("stream1.equals(stream2) = " + stream1.equals(stream2));
System.out.println("stream1.classTypeName = " + stream1.getClass().getTypeName());
System.out.println("stream2.classTypeName = " + stream2.getClass().getTypeName());
// 结果
// stream1.equals(stream2) = false
// stream1.classTypeName = java.util.stream.ReferencePipeline$Head
// stream1.classTypeName = java.util.stream.ReferencePipeline$2

很明显,stream1与stream2不是同一个对象,并且他们不是同一个实现类。stream1的实现类为ReferencePipeline$Head,而stream2的实现类为一个匿名内部类,让我们进步一分析其源码,所谓源码之下,无所遁形。

让我们再看看stream2:

通过分析我们可以发现,stream2的实现类是StatelessOp,所以就形成了这样一个结构。

每一次中间操作都会生成一个新的Stream,如果是无状态操作则实现类是StatelessOp,如果是有状态操作则实现类是StatefulOp。

让我们再来看一下他们之间的继承关系。

再聊核心Sink

实际上Stream API内部实现的的本质,就是如何重载Sink的这四个接口方法。

我还是从一个示例开始:

List<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("ligeligeligeligeligeligeligeligeligelige");
list.add("lisilisilisilisilisilisilisilisi");
list.add("wangwu");
list.add("ligejishuligejishuligejishuligejishuligejishuligejishuligejishu");
List<String> resultList = list.stream()
.filter(it -> it.contains("li"))// 1. 只要包含li的数据
.filter(it -> it.contains("lige"))// 2. 只要包含lige的数据
.map(String::toUpperCase)// 3. 对符合的数据作进一步加工,转换大写
.map(String::toLowerCase)// 4. 对符合的数据作进一步加工,转换小写
.collect(Collectors.toList());
resultList.forEach(System.out::println);

不管是filter方法,还是map方法,还是其他的方法,我们进入到源码层面,返回了一个StatelessOp对象或StatefulOp对象。

所以便产生了这样一个结构:

但是和Sink有什么关系呢?我们再反过来看filter或者map源码:

直接返回一个匿名StatelessOp对象,实现opWrapSink方法,opWrapSink方法是传入一个sink对象,返回另一个sink对象。而新的sink对象拥有传入sink对象的引用。

但是,这个代码有什么用?什么时候触发的呢?

别着急,让我们从collect(Collectors.toList())方法开始一步一步深入研究。

这里我们需要知道传入xx方法的终端对象是ReduceOp,并且这个ReduceOp对象在makeSink的时候返回了一个匿名内部类ReducingSink对象。

这里的makeSink我们提到过,返回一个匿名内部类ReducingSink对象。

先执行warpSink,再执行copyInto。直白一点就是先对Sink进行包装成链式Sink,再遍历Sink链进行copy到结果对象里。这里的两个步骤都很核心。

先看warpSink:

  1. 首次进入时,this为最后的Stream对象,从尾部向头部遍历
  2. 每次遍历时,得到一个新的Stream对象,一般为StatelessOp对象或StatefulOp对象
  3. 执行操作对象的opWrapSink方法,这就是匿名实现了。
  4. 在每一个opWrapSink实现方法中,传入了上一个sink,最终得到一个sink链表

最后,返回Sink链的头节点,内部称之为包装好的sink,命名wrapped,随后,准备进行执行begin,forEachRemaining,end方法。

forEachRemaning最终调用accept方法。

动画理解Stream执行流程

 

点击关注,第一时间了解华为云新鲜技术~

从原理剖析带你理解Stream的更多相关文章

  1. 手摸手带你理解Vue的Computed原理

    前言 computed 在 Vue 中是很常用的属性配置,它能够随着依赖属性的变化而变化,为我们带来很大便利.那么本文就来带大家全面理解 computed 的内部原理以及工作流程. 在这之前,希望你能 ...

  2. 手摸手带你理解Vue的Watch原理

    前言 watch 是由用户定义的数据监听,当监听的属性发生改变就会触发回调,这项配置在业务中是很常用.在面试时,也是必问知识点,一般会用作和 computed 进行比较. 那么本文就来带大家从源码理解 ...

  3. JVM Attach实现原理剖析

    本文转载自JVM Attach实现原理剖析 前言 本文旨在从理论上分析JVM 在 Linux 环境下 Attach 操作的前因后果,以及 JVM 为此而设计并实现的解决方案,通过本文,我希望能够讲述清 ...

  4. 漫谈grpc 3:从实践到原理,带你参透 gRPC

    ​ 原文链接:万字长文 | 从实践到原理,带你参透 gRPC 大家好,我是煎鱼. gRPC 在 Go 语言中大放异彩,越来越多的小伙伴在使用,最近也在公司安利了一波,希望这一篇文章能带你一览 gRPC ...

  5. ARouter原理剖析及手动实现

    ARouter原理剖析及手动实现 前言 路由跳转在项目中用了一段时间了,最近对Android中的ARouter路由原理也是研究了一番,于是就给大家分享一下自己的心得体会,并教大家如何实现一款简易的路由 ...

  6. Kotlin泛型与协变及逆变原理剖析

    在上一次https://www.cnblogs.com/webor2006/p/11234941.html中学习了数据类[data class]相关的知识,这次会学习关于泛型相关的东东,其中有关于泛型 ...

  7. 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】

    原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...

  8. 【Xamarin 跨平台机制原理剖析】

    原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...

  9. iPhone/Mac Objective-C内存管理教程和原理剖析

    http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...

随机推荐

  1. HDLBits->Verilog Language->Modules:Hierarchy->Modules and vectors

    题目要求如上不再赘述,主要关注到最后的四选一多路选择器. 最初编写的选择器代码如下 always@(sel) case(sel) 2'd0:q <= d; 2'd1:q <= in1; 2 ...

  2. raid划分及创建

    RAID 的划分 RAID 0 - RAID 0是最早出现的,是数据分条技术.组建磁盘阵列中最简单的一种形式,可以提高整个磁盘的性能和吞吐量,利用率100%,缺点:一但磁盘损坏,raid0将失效,数据 ...

  3. labelimg使用指南

    labelimg使用指南 From RSMX - https://www.cnblogs.com/rsmx/ 目录 labelimg使用指南 1. 确保已经安装了 Python 环境 2. 使用pip ...

  4. 用Python实时获取Steam特惠游戏数据,我看看谁的钱包还有钱

    前言 大家好鸭, 我是小熊猫 Steam大家应该不陌生吧?不知道的话就让我们来了解一下吧~(一下简称"S") S是由美国电子游戏商Valve于2003年9月12日推出的数字发行平台 ...

  5. 给妹子讲python-S01E01好用的列表

    1.python中的容器数据类型概述2.列表类型的异构性.有序性和本地可变性三大优势2.列表的基本操作(增.删.改.分片索引)3.列表的分片赋值与本地排序 [妹子说]今天开始学python啦,不过我们 ...

  6. Maven配置【详细】

    参考网址:https://www.jianshu.com/p/f2f52a062d5b

  7. 深度学习基础-基于Numpy的感知机Perception构建和训练

    1. 感知机模型   感知机Perception是一个线性的分类器,其只适用于线性可分的数据.          f(x) = sign(w.x + b) 其试图在所有线性可分超平面构成的假设空间中找 ...

  8. 如何准备论文线上Presentation视频录制教程(Summary of Video Recording)

    0:前言 由于国外的疫情严重,目前大多数学术会议都是线上举办,因此往往需要制作presentation的视频录制.由于各种软件横飞,有的需要会员并且不熟悉操作,特别浪费时间.因此,我将这次的操作和遇到 ...

  9. 利用MATLAB仿真节点个数和节点通信半径与网络连通率的关系

    一.目的 ①在不同节点个数的情况下,用Matlab拟合出连通率与通信半径的关系曲线. ②在不同节点通信半径的情况下,用Matlab拟合出连通率与节点个数的关系曲线. 二.方法描述 在1x1的单位矩形中 ...

  10. C#请求HTTPS地址的故障分析和TLS知识点总结

    背景介绍 近期收到同事反馈,在C#程序中通过HTTPClient请求一个HTTPS的地址时,在本地开发环境和测试环境均能正常执行,而部署到生产环境后发生异常且稳定复现,异常提示为:[请求被中止: 未能 ...