草庐IT

从原理剖析带你理解Stream

华为云开发者社区 2023-03-28 原文
摘要: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. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  2. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  3. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  4. ruby - 易于初学者理解的 Ruby 库 - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭3年前。Improvethisquestion我正处于学习Ruby的阶段,我想查看一些小型库的源代码以了解它们是如何构建的。我不知道什么是小型图书馆,但希望SO能推荐一些易于理解的图书馆来学习。因此,如果有人知道一两个非常小的库,这是新手Rubyists学习的好例子,请推荐!我想使用Manveru'sInnatelib,因为它试图保持在2000LOC以下,但我还不熟悉其中经常使用的Ruby速记。也许大约100-5

  5. ruby - 无法理解 `puts{}.class` 和 `puts({}.class)` 之间的区别 - 2

    由于匿名block和散列block看起来大致相同。我正在玩它。我做了一些严肃的观察,如下所示:{}.class#=>Hash好的,这很酷。空block被视为Hash。print{}.class#=>NilClassputs{}.class#=>NilClass为什么上面的代码和NilClass一样,下面的代码又显示了Hash?puts({}.class)#Hash#=>nilprint({}.class)#Hash=>nil谁能帮我理解上面发生了什么?我完全不同意@Lindydancer的观点你如何解释下面几行:print{}.class#NilClassprint[].class#A

  6. ruby - 如何理解 Ruby 中的发送者和接收者? - 2

    我很难理解Ruby中sender和receiver的实际含义。它们一般是什么意思?到目前为止,我只是将它们理解为方法调用和获取其返回值的调用。但是,我知道我的理解还远远不够。谁能给我一个Ruby中发送者和接收者的具体解释? 最佳答案 面向对象中的一个核心概念是消息传递和早期概念化,这在很大程度上借鉴了计算的Actor模型。艾伦·凯(AlanKay)创造了面向对象一词并发明了最早的OO语言之一SmallTalk,他拥有voicedregretatusingatermwhichputthefocusonobjectsinsteadofo

  7. ruby-on-rails - Rails - 理解 application.js 和 application.css - 2

    rails新手。只是想了解\assests目录中的这两个文件。例如,application.js文件有如下行://=requirejquery//=requirejquery_ujs//=require_tree.我理解require_tree。只是将所有JS文件添加到当前目录中。根据上下文,我可以看出requirejquery添加了jQuery库。但是它从哪里得到这些jQuery库呢?我没有在我的Assets文件夹中看到任何jquery.js文件——或者直接在我的整个应用程序中没有看到任何jquery.js文件?同样,我正在按照一些说明安装TwitterBootstrap(http:

  8. ruby - 你如何理解 Ruby 中的这个三元条件? - 2

    我在某些代码中遇到了三元组,但我无法理解条件:str.split(/',\s*'/).mapdo|match|match[0]==?,?match:"somestring"end.join我确实理解我是在某些点上拆分字符串并将总结果转换为数组,然后依次处理数组的每个元素。除此之外,我不知道发生了什么。 最佳答案 一种(稍微)不那么令人困惑的写法是:str.split(/',\s*'/).mapdo|match|ifmatch[0]==?,matchelse"somestring"endend.join我认为多行三元语句很糟糕,尤其是

  9. ruby - 您如何将 S3 理解为 Ruby 中的分层目录结构? - 2

    有没有人成功地将S3存储桶读取为子文件夹?文件夹1--子文件夹2----文件3----文件4--文件1--文件2文件夹2--子文件夹3--文件5--文件6我的任务是读取文件夹1。我希望看到子文件夹2、文件1和文件2,但看不到文件3或文件4。现在,因为我将存储桶键限制为prefix=>'folder1/',你仍然会得到file3和4,因为它们在技术上具有folder1前缀。似乎真正做到这一点的唯一方法是吸收folder1下的所有键,然后使用字符串搜索从结果数组中实际排除file3和file4。有没有人有过这方面的经验?我知道像Transmit和Cyber​​duck这样的FTP风格的S3

  10. 【Unity游戏破解】外挂原理分析 - 2

    文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat

随机推荐