Java stream原理

常用的stream方法

中间操作无状态unordered、filter、map、mapToInt、mapToLong、mapToDouble、flatMap、flatMapToInt、flatMapToLong、flatMapToDouble、peek
中间操作有状态distinct、sorted、limit、skip
终止操作非短路操作forEach、forEachOrdered、toArray、reduce、collect、max、min、count
终止操作短路操作anyMatch、allMatch、noneMatch、findFirst、findAny

有无状态指的是元素的处理受不受之前元素的影响。
短路和非短路指的是遇到符合条件的元素就返回。

需要解决的问题

  1. 如何记录每次操作
  2. 操作如何叠加
  3. 叠加后的操作如何执行
  4. 最后结果如何存储

stream包的分类

  1. 主要是各种操作的工厂类、数据的存储结构以及收集器的工厂类等;
  2. 主要用于Stream的惰性求值实现;
  3. Stream的并行计算框架;
  4. 存储并行流的中间结果;
  5. 终结操作的定义

解决问题

如何记录每次操作

使用stage标记每一次的操作,而stream又需要一个callback,因此完整的操作是由<DataSource、Ops、Callback>三元组表示,具体实现时,使用实例化的ReferencePipeline来表示,如

@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    Objects.requireNonNull(mapper);
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
            return new Sink.ChainedReference<P_OUT, R>(sink) {
                @Override
                public void accept(P_OUT u) {
                    downstream.accept(mapper.apply(u));
                }
            };
        }
    };
}

如何叠加

stage记录了每个操作,但是没有执行的逻辑,因此定义了Sink接口,如下:

interface Sink<T> extends Consumer<T> {
    /**
     * Resets the sink state to receive a fresh data set.  This must be called
     * before sending any data to the sink.  After calling {@link #end()},
     * you may call this method to reset the sink for another calculation.
     * @param size The exact size of the data to be pushed downstream, if
     * known or {@code -1} if unknown or infinite.
     *
     * <p>Prior to this call, the sink must be in the initial state, and after
     * this call it is in the active state.
     */
    default void begin(long size) {}

    /**
     * Indicates that all elements have been pushed.  If the {@code Sink} is
     * stateful, it should send any stored state downstream at this time, and
     * should clear any accumulated state (and associated resources).
     *
     * <p>Prior to this call, the sink must be in the active state, and after
     * this call it is returned to the initial state.
     */
    default void end() {}

    /**
     * Indicates that this {@code Sink} does not wish to receive any more data.
     *
     * @implSpec The default implementation always returns false.
     *
     * @return true if cancellation is requested
     */
    default boolean cancellationRequested() {
        return false;
    }

    /**
     * Accepts an int value.
     *
     * @implSpec The default implementation throws IllegalStateException.
     *
     * @throws IllegalStateException if this sink does not accept int values
     */
    default void accept(int value) {
        throw new IllegalStateException("called wrong accept method");
    }
}

如何执行

调用Sink内的方法组合执行:

@Override
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
    Objects.requireNonNull(wrappedSink);

    if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
        wrappedSink.begin(spliterator.getExactSizeIfKnown());
        spliterator.forEachRemaining(wrappedSink);
        wrappedSink.end();
    }
    else {
        copyIntoWithCancel(wrappedSink, spliterator);
    }
}

如何存储

Sink下的Consumer、Node、SpinedBuffer

  1. 对于返回boolean或者Optional的操作的操作,由于值返回一个值,只需要在对应的Sink中记录这个值,等到执行结束时返回就可以了
  2. 对于collect(), reduce(), max(), min()归约操作,存放在用户调用时指定的容器中
  3. 对于返回是数组的情况,存储在一种叫做Node的数据结构中的。

并行执行

stream的并行执行是基于ForkJoin框架来实现的。

影响并行执行的因素:

  1. 数据大小:数据够大,每个管道处理时间够长,并行才有意义;
  2. 源数据结构:每个管道操作都是基于初始数据源,通常是集合,将不同的集合数据源分割会有一定消耗;
  3. 装箱:处理基本类型比装箱类型要快;
  4. 核的数量:默认情况下,核数量越多,底层fork/join线程池启动线程就越多;
  5. 单元处理开销:花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显;

源数据结构分类:

  1. 性能好:ArrayList、数组或IntStream.range(数据支持随机读取,能轻易地被任意分割)
  2. 性能一般:HashSet、TreeSet(数据不易公平地分解,大部分也是可以的)
  3. 性能差:LinkedList(需要遍历链表,难以对半分解)、Stream.iterate和BufferedReader.lines(长度未知,难以分解)

Work Stealing原理:

  1. 每个工作线程都有自己的工作队列WorkQueue,这是一个双端队列dequeue,它是线程私有的;
  2. ForkJoinTask中fork的子任务,将放入运行该任务的工作线程的队头,工作线程将以LIFO的顺序来处理工作队列中的任务,即堆栈的方式;
  3. 为了最大化地利用CPU,空闲的线程将从其它线程的队列中「窃取」任务来执行,但是是从工作队列的尾部窃取任务,以减少和队列所属线程之间的竞争;
  4. 双端队列的操作:push()/pop()仅在其所有者工作线程中调用,poll()是由其它线程窃取任务时调用的;
  5. 当只剩下最后一个任务时,还是会存在竞争,是通过CAS来实现的;

java8Stream原理深度解析
https://www.cnblogs.com/Dorae/p/7779246.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值