JAVA8必看~~效率工具类Stream底层原理超详细傻瓜式解析

简介

    说起 Java 8,我们知道 Java 8 大改动之一就是增加函数式编程,而 Stream API 便是函数编程的主角,Stream API 是一种流式的处理数据风格,也就是将要处理的数据当作流,在管道中进行传输,并在管道中的每个节点对数据进行处理,如过滤、排序、转换等。

    但很多人只知道Stream的用法,不知其原理,随便使用的话就有可能出现问题。

    在踩过一次坑后,决心对这个经常使用的工具类的底层来一次梳理,加深一下理解,以防再次踩坑T T。

Stream原理解析

    阅读源码前,先了解一下stream的一些基本知识。

    Stream类结构

    下面是几个实现Stream关键步骤的几个类,可以先记住下面类和类之间的关系,方便理解
在这里插入图片描述1234556
BaseStream定义了流的迭代、并行、串行等基本特性;

Stream中定义了map、filter、flatmap等用户关注的常用操作;

PipelineHelper用于执行管道流中的操作以及捕获输出类型、并行度等信息

Head、StatelessOp、StatefulOp为ReferencePipeline中的内部子类,用于描述流的操作阶段。

    操作分类

     Stream中的操作可以分为两大类:中间操作(Intermediate operations)与结束操作(Terminal operations),中间操作只是对操作进行了记录,只有结束操作才会触发实际的计算(即惰性求值),这也是Stream在迭代大集合时高效的原因之一。中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
     结束操作又可以分为短路(short-circuiting)与非短路操作,这个应该很好理解,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。

    之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。
在这里插入图片描述

    例子

下面通过一个例子来看一下,平时我们使用的Stream到底是怎么运转的吧

public class TestStream {
    public static void main(String[] args) {
       testCollect();
    }

    public static void testCollect(){
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        List<Object> collect = Optional.ofNullable(list)
                .orElse(Collections.emptyList())
                .stream()
//                .map(i -> null)
                .map(i -> i+1)
                .filter(i -> i<3)
                .collect(Collectors.toList());
        System.out.println(collect);
    }
}

    这个例子中我们分别使用两个无状态操作map和filter以及一个collect用作结束操作来将集合中的数据进行处理成我们想要的集合。今天我们仅对串行的流式处理作学习。

    创建流Stream()

    在开始流式操作之前,我们先会创建一个流,这个方法很简单,只是创建了一个Head头节点,并用parallel判断是串行还是并行进行流式操作。
在这里插入图片描述在这里插入图片描述

    往下继续看,我们发现这个Head节点最后会调用AbstractPipeLine的构造方法,我们查看AbstractPipeLine这个类发现,AbstractPipeLine成员变量中持有前一个节点,后一个节点以及头节点的引用。这不是一个典型的双向链表吗?

 @SuppressWarnings("rawtypes")
    private final AbstractPipeline sourceStage;//头节点

    /**
     * The "upstream" pipeline, or null if this is the source stage.
     */
    @SuppressWarnings("rawtypes")
    private final AbstractPipeline previousStage;//前一个节点

    /**
     * The operation flags for the intermediate operation represented by this
     * pipeline object.
     */
    protected final int sourceOrOpFlags;

    /**
     * The next stage in the pipeline, or null if this is the last stage.
     * Effectively final at the point of linking to the next pipeline.
     */
    @SuppressWarnings("rawtypes")
    private AbstractPipeline nextStage;//下一个节点

    /**
     * The number of intermediate operations between this pipeline object
     * and the stream source if sequential, or the previous stateful if parallel.
     * Valid at the point of pipeline preparation for evaluation.
     */
    private int depth;//深度

最后我们会创建一个头节点指向自己,深度为0的头节点出来,Stream流的创建也就完成了。
在这里插入图片描述

    处理操作

map()
    下面我们看看数据在流中是怎么被处理的?例子中,我们开始会对集合中的数据用map操作进行处理

    由于map()是个无状态操作,所以这一步会创建一个StateLessOp对象,并实现他的opWrapSink方法。

    opWrapSink方法就是真正处理数据的方法,这个之后会讲。我们可以先关注map这个动作仅仅只是执行了Function中的lamda表达式对流中的数据u进行了简单处理。
在这里插入图片描述
    StatelessOp这个对象的构造方法最后也是聚焦到AbstractPipeLine的构造方法上,将刚刚创建的Stream流指向当前创建的PipeLine对象,最后返回这个无状态操作StatelessOp对象。
在这里插入图片描述
这样链表中就有两环了
在这里插入图片描述
filter()
    filter()和map()一样,也是在链表中进行套环,只是对数据的处理逻辑不同。
最后创建一个无状态对象进行返回,并在链表中再增加一环,深度加一
在这里插入图片描述
后续不管多少操作,也只是在链表中增加节点个数,直到进行结束操作。最后形成的链表结构如下:
在这里插入图片描述

collect()
    最后来看看最关键的结束操作,这块比较绕,需要加强记忆。以常用的collect()为例

    举例中我们使用Collectors.toList()将数据打包成一个新集合,这个方法会生成一个collector对象

public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

先记住这个CollectorImpl对象的两个lamda成员变量 ArrayList::new, 和List::add 最后会用这两个动作去构建新集合

    然后这个collector对象会被封装成一个ReduceOps对象,作为最底层的节点为后续计算使用。
在这里插入图片描述
evaluate方法里会判断一下采用并行还是串行处理流中的数据
在这里插入图片描述
    选择串行处理会调用刚创建的ReduceOps对象中的makeSink方法
在这里插入图片描述
makeSink方法会创建一个ReducingSink对象,这个对象有着刚刚创建collector对象中的所有动作,用于新集合的创建和添加对象
在这里插入图片描述
再来看看最后处理前的关键一步,wrapSink方法会把刚刚的Sink对象包装成一个含有map、filter等全部操作的sink对象。
在这里插入图片描述
具体是怎么做的呢?这里采取了一种遍历思想,从链表的最后开始一直到深度为0,层层包装递归成一个有着全部操作的sink对象
在这里插入图片描述
这里opWrapSink操作就是之前filter创建的StatelessOp中实现的方法,也就是链表中最后一个节点。
在这里插入图片描述
StateLessOp构造方法会将链表中后一个sink对象装到现在这个节点的成员变量downstream(也就是下一个流操作此处对应filter)里面去
在这里插入图片描述
这样迭代完之后得到一个从头节点开始执行操作的对象,该对象持有下一个节点的sink的引用用于执行下一次操作
在这里插入图片描述
begin执行的是创建新集合的操作,对应上文的ArrayList::new操作
在这里插入图片描述
在这里插入图片描述
接下来,不断迭代流中的操作,根据链表中的顺序进行操作,最后得到最终的数据。
在这里插入图片描述
这一步实现将数字+1,传给filter操作进行过滤
在这里插入图片描述
这一步将符合过滤条件的数据传给有collector行为的sink对象(最初传入的那个)
在这里插入图片描述
最后调用List::Add,将这个数据加入到集合中,数据全部加入集合后,调用end方法结束流程,最后返回这个集合。十分清晰干净的代码。
在这里插入图片描述
在这里插入图片描述

所有流程到这里就结束了。

总结

1.Stream的底层原理是用双向链表实现的,可以根据需要串行或并行执行操作。

2.Stream为数据的处理专门设置了一个Sink接口以及对应的实现类,会将所有操作递归封装到一个sink类里面去。从上往下依次调用无状态和有状态操作

3.Stream最后需要一个结束操作来结束流程

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值