Stream流转原理

from 曾经对面的同事
每个Stream包含三个阶段 源、零个或多个中间操作、终止操作
源阶段

首先看一个例子,这里的例子非常简单,就是将List构造成了一个Stream,然后进行循环,
但是它是如何就将list构造成了Stream

List<String>stringList = new ArrayList<>();
stringList.add("颜智慧");
stringList.add("菜穗子");
stringList.stream().forEach(System.out::println);

点进stream()方法

这是JDK1.8在Collection中新增加的默认方法,用于将集合构造成Strem对象,方法大体文档注释如下,构造关键在于spliterator()方法

/**
 * 返回一个以此集合为源的流对象
 * 当这个方法无法返回IMMUTABLE和CONCURRENT特性值时应当被重写
 * 默认会以集合的顺序创建流对象
 * 默认串行
 */
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

spliterator()方法也是Collection中新增加的默认方法,它重写了Iterable中的默认实现,创建了一个Spliterator对象,E代表集合中元素的类型

/**
 * 以当前集合为源创建Spliterator对象
 * Spliterator对象应当包含某些特性值,但是如果集合为空时就不需要包含SIZED特性值
 * 这个方法应该被子类重写,应该具有不可变或是并发修改、延迟绑定、快速失败的特征,为了保证这个流与Collection中元素是等价的
 * 空的spliterator()只会包含SIZED和SUBSIZED特性值
 */
@Override
default Spliterator<E> spliterator() {
    return Spliterators.spliterator(this, 0);
}

可以看出Spliterator非常重要,它用于collection流源的构建,其实其它的Stream构造也是利用了Spliterator接口,我们来详细通读一遍Spliterator接口的文档和方法说明;

Spliterator 分割迭代器

下面是Spliterator接口中的接口和一些特性值,方法描述我以注释的形式标识出来

package com.tinny.jdk8.spliterator;

import java.util.Comparator;
import java.util.function.Consumer;

/**
 * 名词解释:
 * Spliterator 以下简称Sp 
 * structural interference 译为:结构被修改,也就是数据源被修改
 * <p>
 * Spliterator文档解析描述
 * <p>
 * 它是一个用于分区和遍历源数据的对象,源可以是数组、集合或者是io函数;
 * <p>
 * 可以通过tryAdvance单独遍历或是forEachRemaining循环遍历;
 * <p>
 * 可以将一个Sp分裂成两个平行的Sp,用于并行计算,如果分裂代价过大或者分裂没有意义对并行来说就是负担,
 * 每个Sp只会作用到属于自己的那一块区域;
 * <p>
 * 它有一些特性值,例如ORDERED、DISTINCT等,用于简化计算,类似Collectors中特性值含义,
 * 例如来自List的Sp就会包含ORDERED,Set就会包含DISTINCT等;
 * <p>
 * 某些特性值会有特殊含义,例如ORDERED就代表会被顺序执行,JDK告诉我们不要去定义新的特性,老老实实用它现在的就行了;
 * <p>
 * <p>
 * 如果一个Sp不包含IMMUTABLE或者CONCURRENT,会在绑定之后进行源数据的检测,
 * 一个延迟绑定的Sp绑定数据源出现方法的第一次遍历、分割或是第一次查询大小,而不是第一次创建,
 * 一个非延迟绑定的Sp,绑定数据在于构造或是任意方法第一次调用,
 * 如果绑定之前修改的数据源,那么绑定后的数据也会被改变,但是如果绑定之后检测到数据结构被修改,就会抛出ConcurrentModificationException异常,
 * Sp有快速失效策略,例如forEachRemaining方法,Sp会优化遍历策略,并不是在每次调用元素时就会检测一次,这样效率比较低,它是在所有元素执行完毕之后再进行检测;
 * <p>
 * Sp提供一个估值的操作,如果包含SIZED特性,这个估值就是代表所有遇到的元素,如果不包含也会提供一个大概的值用于分割;
 * <p>
 * 虽然Sp分裂非常适合于并行操作,但是它不是线程安全的,需要由调用者来保证,通常都是使用顺序调用的方式来避免线程安全问题,Sp分裂后可以进行再次分裂,
 * 分裂最好都是在元素被消费之前进行分裂,因为元素被消费后,分裂出对应的SIZED往往是不准确的;
 * <p>
 * 为了提高效率,减少装箱拆箱的损耗,提供了OfInt、OfLong、OfDouble的特例,具体细节可以看代码;
 * <p>
 * Sp类似于Iterator,用来进行元素遍历,它提供了效率更高的并行分割遍历的方式,抛弃了Iterator的hasNext()和next()方法,避免线程竞争,
 * Iterator会通过两次校对hasNext()和next()获取下一个元素,Sp将其简化为一个方法,具体哪个方法我也没细看。。TODO Sp的简化遍历的方法;
 * <p>
 * 对于可变源,如果在源绑定到消费这个过程中,这个过程中源结构如果发生变化(替换、新增、删除),结果就会具备不确定性;
 * <p>
 * 可变源可以通过如下几种方式来控制
 * 1、源结构不能被修改 CopyOnWriteArrayList是一个不可变的源,通过复制重写的方式来实现源结构更新,通过这种方式避免了线程竞争的方式,适合于读多写少的场景;
 * 2、可以并发修改的源 ConcurrentHashMap通过分段锁来控制每一片bucket;
 * 3、延时绑定和快速失败的源 ArrayList以及大部分的Collection的子类都具备这个特性,会在源绑定之后元素消费之前如果检测到结构被修改,会遵循快速失效的策略;
 * 4、非延时绑定和快速失败的源 类似3,但是由于它绑定时机更早,所有从元素绑定到检测时间会更长
 * 5、延迟绑定和非快速失败的源 在绑定后进行遍历(消费)中,如果元素发生的变化,由于没有快速失败策略,后续的行为是不确定的;
 * 6、非延时绑定和非快速失败的源 在构造或是某个方法调用时就会被绑定,中间元素改变后,后续的行为也是不确定的;5,6两种都是有风险的
 * <p>
 * 下面有两个例子介绍了Sp和并行分割基本用法,细节可以自己去看
 */
public interface Spliterator<T> {
    /**
     * 尝试获取元素
     * 如果元素存在,就会执行给定操作,并返回true;元素不存在,就会返回false;
     * 异常会返回给调用者
     *
     * @param action 给定动作
     */
    boolean tryAdvance(Consumer<? super T> action);

    /**
     * 对于每个遇到的元素会在当前线程执行给定的动作,也就是循环;
     * 在所有元素消费完或是抛出异常后停止;
     *
     * @param action 给定动作
     */
    default void forEachRemaining(Consumer<? super T> action) {
        do {} while (tryAdvance(action));
    }

    /**
     * 尝试分割
     * 如果当前Sp可以被分割,当前的Sp会被截取,返回一个全新的sp并持有一定数量的元素;
     * 如果当前Sp包含ORDERED,返回的元素也必须包含;
     * 除非当前Sp是一个无限流,否则重复调用trySplit()最终会返回一个null;
     * 拆分前的estimateSize,必须大于或是等于拆分后Sp的estimateSize;
     * 如果这个sp包含SUBSIZED,那么分裂前的estimateSize必须等于分裂后estimateSize的总和;
     * 可能会因为任意原因返回null,本身就是一个空值、已经是最小分割单元等等原因,提醒我们要做校验;
     *
     * @return New Sp/null
     * @
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值