手把手带你读java源码之JAVA-stream数据结构和初始化源码详解(万字长文详解)

news2024/11/18 0:00:17

手把手带你读java源码之JAVA-stream数据结构和初始化源码详解(万字长文详解)

stream

stream是java8新增的非常重要的一个特性。并且非常的常用。它实现了函数式编程。具体函数式编程的概念已经很久了,比如js中的箭头函数。java中也通过stream做出了支持。想深入理解的可以参考cmu的课程15-150或者stanford的CS 95SI。

它可以帮助我们方便的处理很多东西。处理分为两种,中间态和结果态。

在这里插入图片描述

下面是一些中间态操作。他们位于链式操作的中间,当调用他们的时候并没有真正执行。只有当调用结果态的方法的时候才会真正的执行操作,也就是所谓的延迟执行

方法名说明
map循环。可以简单理解为foreach
flatMap将二维数据展开成一维
filter过滤数据
distinct去重
sorted排序
limit限制只取n个元素
skip跳过n个元素

stream的初始化

看一个例子,假设我们有一个需求。输出大于5的所有数。

  • 期望输入:2,5,7,1,3,2,8
  • 期望输出:7,8
//首先初始化输入列表
List<String> list = new ArrayList<>();
list.add("2");
list.add("5");
list.add("7");
list.add("1");
list.add("3");
list.add("2");
list.add("8");

//开始执行操作
List<Integer> list2 = list.stream().map(Integer::valueOf).filter(x -> {
            return x > 5;
        }).collect(Collectors.toList());

//输出
System.out.println(list2);

接下来看一下stream是如何执行的。下面是stream的一个类图。可以看到初始化需要使用到一个接口和6个类。主要分为三大类。

在这里插入图片描述

类介绍

第一类是ArrayList类和ArrayListSpliterator类。这两个类是核心类。毕竟我们输入类型是ArrayList,这个就不用说了。

主要在于ArrayListSpliterator这个类,这个类是ArrayList的一个内部类。最主要的操作方法和数据都在里面。看一下几个属性

  • list 我们要进行stream操作的list
  • fence 大小
  • expectedModCount 期望的处理数量
  • index

最主要的循环处理方法同样在这个类里面。处理逻辑全部在forEachRemaining这个方法中。

第二大类是StreamSupport流的支持类,算是一个单独的类,提供了对stream的一些操作方法,比如初始化一个stream。

第三大类是AbstractPipeline抽象类为主的3个类,还有两个是继承自AbstractPipelineReferencePipeline类,主要负责处理引用类的流。和继承ReferencePipelineHead类,实现了双向链表的头节点。他们的主要功能就是构造为流的双向链表数据结构。

在这里插入图片描述

执行流程介绍

这几个类的执行时序图如下。

在这里插入图片描述

可以清晰的看到,通过Collection类的stream方法调用到了ArrayList的方法然后调用到了ArrayListSpliterator的方法,来初始化了ArrayListSpliterator对象,并存储到流中。

接下来Collection类将初始化好的ArrayListSpliterator对象传递给了StreamSupport类用来初始化stream。

StreamSupport将会创建一个双向链表的头节点。并将ArrayListSpliterator对象放入头节点。初始化以后的流如下图所示:

在这里插入图片描述

流介绍

流分为两种,顺序流和并行流。

顺序流

顺序流顾名思义就是按照顺序执行。可以直接的类比为for循环。如下图,如果1,2,3三个元素,进入流以后,依然是1,2,3三个元素。

在这里插入图片描述

并行流

并行流是充分的利用现代多核计算机的性能而出的。它可以把流分散到各个进程/线程中去执行。来达到并行执行的效果。如下图,1,2,3三个元素,可能会进入2个流中。

在这里插入图片描述

源码分析

list的stream方法调用的是Collection类的stream方法。所以首先来看这个方法,该方法返回一个顺序流,顺序流中包含了list中的所有元素。

//该方法返回一个顺序流,顺序流中包含了list中的所有元素。
default Stream<E> stream() {
    //调用了StreamSupport.stream方法,传入了一个分割迭代器,第二个参数false代表是顺序流,true是平行流。
    return StreamSupport.stream(spliterator(), false);
}

//生成一个分割迭代器,该方法是ArrayList类中的方法。
@Override
public Spliterator<E> spliterator() {
    //返回一个ArrayListSpliterator类的实例。
    //实例中包含4个属性
    //list = list
    //index = 0
    //fence = -1
    //expectedModCount = 0
    return new ArrayListSpliterator<>(this, 0, -1, 0);
}

接下来来到StreamSupport类的stream方法。通过spliterator来创建一个顺序流。只有当结果态操作开始后,spliterator才会真正开始运行

/**
*  通过spliterator来创建一个顺序流或者并行流,如果parallel=1就是并行流
*  只有当结果态操作开始后,spliterator才会真正开始运行
*  spliterator需要具有不可变性,并发性或延迟绑定性。
*  当前的spliterator就是上面生成的ArrayListSpliterator对象。且包含上面说的4个属性。
**/
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
    //参数校验
    Objects.requireNonNull(spliterator);
    //返回一个ReferencePipeline.Head类型的头节点
    return new ReferencePipeline.Head<>(spliterator,
                                        StreamOpFlag.fromCharacteristics(spliterator),
                                        parallel);
}

在生成Head对象的时候,第二个参数调用了StreamOpFlag类的fromCharacteristics方法。来看一下这个方法。
可以看到有一个判断是否是自然排序的,如果不是自然排序就会走到if里面,也就是不会将spliterator标记为已排序状态。
最终返回结果为80

//将spliterator的characteristic bit转换为stream flags
//当前的spliterator就是上面生成的 ArrayListSpliterator 对象。且包含上面说的4个属性。
static int fromCharacteristics(Spliterator<?> spliterator) {
    //调用spliterator对象的characteristics方法。具体看下面 返回的characteristics = 16464
    int characteristics = spliterator.characteristics();
    // 16464和4做按位与,即两个都是1才是1,100 0000 0101 0000 & 100 = 000 0000 0000 0000 = 0说明不满足第一个条件,触发短路,走else
    if ((characteristics & Spliterator.SORTED) != 0 && spliterator.getComparator() != null) {
        // Do not propagate the SORTED characteristic if it does not correspond
        // to a natural sort order
        // 如果不是自然排序的,则不传播有序状态。
        return characteristics & SPLITERATOR_CHARACTERISTICS_MASK & ~Spliterator.SORTED;
    }
    else {
        //将16464和85做按位与,100 0000 0101 0000 & 0101 0101 = 000 0000 0101 0000 = 80
        //返回80
        return characteristics & SPLITERATOR_CHARACTERISTICS_MASK;
    }
}

//ArrayListSpliterator类的characteristics方法。
//三个数做按位或,即有1就是1,结果是 100 0000 0101 0000 = 16464
public int characteristics() {
    //Spliterator.ORDERED = 16 = 1 0000
    //Spliterator.SIZED = 64 = 100 0000
    //Spliterator.SUBSIZED = 16384 = 100 0000 0000 0000
    return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}

接下来看Head类,它是ReferencePipeline类的一个内部类。主要作用是头节点的一些属性和操作。包括生成头节点等。


static class Head<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> {
    //这个是双向链表的头节点。也就是把最先创建的初始流作为头节点。
    //三个参数,第一个参数source = ArrayListSpliterator对象
    //第二个参数是流的标志,80
    //第三个参数是否并行流 0
    Head(Spliterator<?> source,
            int sourceFlags, boolean parallel) {
        //调用了父类的构造函数,就是ReferencePipeline的构造方法。在下面。
        super(source, sourceFlags, parallel);
    }
}


//ReferencePipeline的构造方法。参数同上,不再描述。
ReferencePipeline(Spliterator<?> source,
                    int sourceFlags, boolean parallel) {
    //再次调用了父类的构造方法。ReferencePipeline的父类是AbstractPipeline类,放在下面了。
    super(source, sourceFlags, parallel);
}

//AbstractPipeline类的构造方法。构造一个stream的头节点。
AbstractPipeline(Spliterator<?> source,
                     int sourceFlags, boolean parallel) {
    //头节点的头指针为空。
    this.previousStage = null;
    //数据 = ArrayListSpliterator对象
    this.sourceSpliterator = source;
    //头节点的数据 指向自己。
    this.sourceStage = this;
    //标志位 = 80 & 85 = 80 二进制 0101 0000 & 0101 0101 = 0101 0000 = 80
    this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
    // The following is an optimization of:
    // StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);
    //sourceOrOpFlags << 1 = 160  左移一位就是 * 2
    //~160 = -161  按位去反
    // -161 & 255 = 95
    // combinedFlags = 95
    this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
    // 双向链表深度 = 0 ,因为现在只有头节点。
    this.depth = 0;
    // 是否并行流 = 0 不是
    this.parallel = parallel;
}

list.stream()函数的源码就完了。主要调用了构造函数,构造了一个含有list数据的头节点,头指针指向空,next指针指向自己。完成了流的初始化。当前数据结构如下。

在这里插入图片描述

stream的中间态

中间态的具体源码和流程在后面介绍,这里只介绍中间态的作用。

中间态的主要作用是构建双向链表的中间节点。一个操作对应一个节点。比如map,就会创建一个节点。其中pre指针指向前一个节点也就是头节点。而头节点的next指针指向map节点。

filter操作的时候同样创建一个节点,pre指针指向上一个操作也就是map节点。map节点的next指针指向filter节点。

每个中间态节点中都存储了操作,也就是中间态的时候传入的函数。而数据则全部在头节点中。

比如下面这样:

在这里插入图片描述

每个中间态节点其实又分成两种

  • 有状态节点
  • 无状态节点

类图如下:

在这里插入图片描述

stream的结果态

结果态的具体源码和流程在后面介绍,这里只介绍结果态的作用。

结果态的主要作用有三个

  1. 构造结果态节点
  2. 构造sink链表
  3. 执行流。

先说第一个,结果态节点是ReduceOp对象。这个结果态节点中包含了一个makeSink方法,用来构建结果态的sink节点。结果态的sink节点是一个ReducingSink对象。

第二个,当结果态节点和结果态的sink节点构造完成以后。接下来会根据之前构建好的双向链表来生成对应的sink链表。

一开始的双向链表我们知道是这样的

在这里插入图片描述

而sink链表则是反过来了,根据双向链表从后向前通过pre指针不断向前,把每个节点包裹在sink节点中并通过downstream指针来指向下个节点。这里因为在第一步的时候把源数据取出来了,所以sink中不包含头节点。创建完后如图所示:

在这里插入图片描述

第三步才是真正的执行流。根据sink链表来执行,每次把元素传递给第一个sink也就是map操作,当第一个sink节点处理完以后通过downstream流动到下一个sink节点执行。不断通过downsteram流动,直到最后到结果态的sink执行完以后。再次把第二个元素进行流动执行。直至所有元素执行完毕。

总结

因为是通过自顶向下的方式来了解stream。所以这里主要介绍了stream的执行流程和初始化的源码分析。中间态和结果态的源码分析放在了后面。

在执行流程中可以看到。首先创建了一个双向链表,然后在根据双向链表创建了sink链表。最后通过sink链表进行执行流的操作。也可以看出来确实是流动,传播,充满了stream的味道。

从这里也能看出来为何一开始是双向链表而不是单向的,因为要通过pre指针构造sink链表。

但是这里就有一个问题了,为什么不直接用一开始的双向链表,而要在创建一个sink链表呢?

我个人觉得有几个原因:

  1. 因为当前处于结果态节点,想从头流动执行,需要当前指针先指向头节点,所以必须遍历一遍。
  2. 重新构建一个纯净的sink链表,来达到不变性的性质。保持之前的数据和节点等不可变。
  3. 双向链表只负责存储数据和操作。真正的执行通过sink链表来执行,达到单一职责分层清晰
  4. 其中或许还有并行流并发的问题。

创作不易,且行且珍惜。如有帮助,随手点个赞和收藏,不胜感激。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云原生安全检测器 Narrows(CNSI)的部署和使用

近日&#xff0c; 云原生安全检测器 Narrows&#xff08;Cloud Native Security Inspector&#xff0c;简称CNSI&#xff09;发布了0.2.0版本。 &#xff08;https://github.com/vmware-tanzu/cloud-native-security-inspector&#xff09; 此项目旨在对K8s集群中的工作负载进…

分布式文件管理系统(MinIO)

1.去中心化&#xff0c;每个点是对等的关系&#xff0c;通过Ngix对负载做均衡工作。 好处&#xff1a; 能够避免单点故障&#xff0c;将多块硬盘组成一个对象存储服务。 2. 使用纠删编码技术来保护数据&#xff0c;是一种回复丢失和损坏的数据的数学算法&#xff0c;他将数据分…

小红书用户画像 | 小红书数据平台

小红书的用户画像是小红书品牌营销的必备技能&#xff0c;也是小红书推广种草的一个重要前提。通过对小红书用户画像进行分析&#xff0c;对品牌进行精准营销&#xff0c;实现更高的流量转化。 2022小红书粉丝人群画像 千瓜数据在2022年发布的千瓜活跃用户画像趋势报告中分析了…

Hive---安装教程

Hive安装教程 Hive属于Hadoop生态圈&#xff0c;所以Hive必须运行在Hadoop上 文章目录Hive安装教程上传安装包解压并且更名修改 /etc/profile创建hive-site.xml将mysql的jar包放入Hive库中开启刷新配置文件hadoop开启mysql初始化启动hive上传安装包 将安装包上传到/opt/insta…

一文搞懂Docker容器里进程的 pid 是如何申请出来的?

如果大家有过在容器中执行 ps 命令的经验&#xff0c;都会知道在容器中的进程的 pid 一般是比较小的。例如下面我的这个例子。 # ps -ef PID USER TIME COMMAND1 root 0:00 ./demo-ie13 root 0:00 /bin/bash21 root 0:00 ps -ef 不知道大家是否和我一样…

始于日志,不止于日志,Elastic Stack全面介绍

1、Elastic Stack是什么&#xff1f; 说Elastic Stack之前&#xff0c;先说一下ELK Stack。这个词相信很多人都是耳熟能详的&#xff0c;作为一个著名的日志系统解决方案&#xff0c;应用非常广泛。 “ELK”是三个开源项目的首字母缩写词&#xff1a;Elasticsearch、Logstash…

第五章.与学习相关技巧—Batch Normalization

第五章.与学习相关技巧 5.3 Batch Normalization Batch Norm以进行学习时的mini_batch为单位&#xff0c;按mini_batch进行正则化&#xff0c;具体而言&#xff0c;就是进行使数据分布的均值为0&#xff0c;方差为1的正则化。Batch Norm是调整各层激活值的分布使其拥有适当的广…

进程组和用处

进程组&#xff1a;一个或多个进程的集合&#xff0c;进程组id是一个正整数。组长进程&#xff1a;进程组id 进程id组长进程可以创建一个进程组&#xff0c;创建该进程组的进程&#xff0c;终止了&#xff0c;只要进程组有一个进程存在&#xff0c;进程组就存在&#xff0c;与…

卷积神经网络(CNN)

目录The Basic Usage of CNNPadding&#xff08;填充&#xff09;Weights&#xff08;权重&#xff09;PoolingThe Basic Usage of CNN What are Convolutional Neural Networks? They’re basically just neural networks that use Convolutional layers&#xff08;卷积层…

家政服务小程序实战教程13-接入客服

小程序在微信里使用&#xff0c;以其无需安装随用随走为特点。但是有个问题是&#xff0c;如果提供商品或者服务的&#xff0c;用户如果有问题往往希望平台的运营方给出专业的解答。为了满足这类需求&#xff0c;就需要我们提供客服接入的功能&#xff0c;用户可以点击客服图标…

Linux使用定时任务监控java进程并拉起

需求描述&#xff1a; 设计一个脚本&#xff0c;通过Linux定时任务&#xff0c;每分钟执行一次&#xff0c;监控jar包进程是否存在&#xff0c;存在则不做动作&#xff0c;不存在则重新拉起jar包程序。 定时任务配置&#xff1a; */1 * * * * bash -x /root/myfile/jars/che…

stk 根据六根数文件生成卫星轨迹(一)

先简单介绍下上面的参数。 Propagator预报轨道模型。 TwoBody为二体&#xff08;开普勒运动模型&#xff09;。HPOP为高精度轨道模型。目前只用到这两个。 下图为六根数参数 Orbit Epoch&#xff1a;为根数时间&#xff08;UTC&#xff09; Semimajor Axis&#xff1a;长半…

软考高项——第五章进度管理

范围管理进度管理总线索规划进度管理定义活动活动排序估算活动资源估算活动时间制定进度管理计划控制进度进度管理总线索 进度管理的总线索包括&#xff1a; 1&#xff09;规划进度管理 2&#xff09;定义活动 3&#xff09;活动排序 4&#xff09;估算活动资源 5&#xff09;…

pandas基本操作

df.head()/tail() 查看头/尾5条数据&#xff1b;df.info 查看表格简明概要&#xff1b;df.dtypes 查看字段数据类型&#xff1b;df.index 查看表格索引&#xff1b;df.columns 查看表格列名&#xff1b;df.values 以array形式返回指定数据的取值&#xff1b;list(dt.groupby(&q…

vue2 使用 cesium 篇

vue2 使用 cesium 篇 今天好好写一篇哈&#xff0c;之前写的半死不活的。首先说明&#xff1a;这篇博文是我边做边写的&#xff0c;小白也是&#xff0c;实现效果会同时发布截图&#xff0c;如果没有实现也会说明&#xff0c;仅仅作为技术积累&#xff0c;选择性分享&#xff0…

远程管理时代,还得是智能化PDU才靠得住!

在如今这个信息技术高速发展的时代&#xff0c;数据中心IDC机房服务器数量与日俱增&#xff0c;提供DNS域名服务、主机托管服务、虚拟主机服务等服务的服务器是IDC最基本的功能之一。服务器需要7*24小时不间断持续工作&#xff0c;但当服务器数量很大&#xff0c;服务器工作、重…

.net6API使用AutoMapper和DTO

AutoMapper&#xff0c;是一个转换工具&#xff0c;说到AutoMapper时&#xff0c;就不得不先说DTO&#xff0c;它叫做数据传输对象(Data Transfer Object)。 通俗的来说&#xff0c;DTO就是前端界面需要用的数据结构和类型&#xff0c;而我们经常使用的数据实体&#xff0c;是数…

华为ensp模拟校园网/企业网实例(同城灾备及异地备份中心保证网络安全)

文章简介&#xff1a;本文用华为ensp对企业网络进行了规划和模拟&#xff0c;也同样适用于校园、医院等场景。如有需要可联系作者&#xff0c;可以根据定制化需求做修改。作者简介&#xff1a;网络工程师&#xff0c;希望能认识更多的小伙伴一起交流&#xff0c;私信必回。一、…

多元回归分析 | CNN-LSTM卷积长短期记忆神经网络多输入单输出预测(Matlab完整程序)

多元回归分析 | CNN-LSTM卷积长短期记忆神经网络多输入单输出预测(Matlab完整程序) 目录 多元回归分析 | CNN-LSTM卷积长短期记忆神经网络多输入单输出预测(Matlab完整程序)预测结果评价指标基本介绍程序设计参考资料预测结果 评价指标 训练集平均绝对误差MAE:0.69559 训练…

宝塔搭建实战php源码人才求职管理系统后台端thinkphp源码(一)

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 在开源社区里看到了这一套系统&#xff0c;骑士人才系统SE版&#xff0c;搭建测试了&#xff0c;感觉很不错。能够帮助一些想做招聘平台的朋友降低开发成本&#xff0c;就是要注意&#xff0c;想商业使用的话&…