Stream流中的归约reduce方法累加理解


一,分析

在这里插入图片描述
Stream中的三个归约方法如上所示,

java8 流相关的操作中,我们把它理解 “累加器”,之所以加引号是因为他并不仅仅是加法,他的运算可以是一个Lambda 表达式,所以更准确的说 reduce 是一个迭代运算器
一个reduce操作(也称为折叠)接受一系列的输入元素,并通过重复应用操作将它们组合成一个简单的结果

参照reduce方法文档给出的示例

T result = identity;

for (T element : this stream)
	result = accumulator.apply(result, element)
	
return result;

1,累加的概念

以两个参数的reduce为例:
在这里插入图片描述
BinaryOperator 是BiFunction 的三参数特殊化形式,两个入参和返回结果都是类型T,即为同一种类型。

来看下面的例子:

计算1,2,3,4,5 的和,并且初始值为3
也就是计算3+1+2+3+4+5

⑴.使用Stream 两个参数的reduce方法进行归约运算
⑵.使用for循环迭代调用BinaryOperator 的apply进行运算:

    @Test
    public void test(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        //⑴ 归约方法----------------------------------------------------------------------------
        Integer reduce = list.stream().reduce(3, (x, y) -> x + y);
        System.out.println(reduce);  // 18

        //⑵ 循环累加----------------------------------------------------------------------------
        //函数式接口实现  accumulator
        BinaryOperator<Integer> accumulator = (x,y) -> x+y;
        //初始值
        int identity = 3;
        //遍历
        for(Integer ele : list){
            identity = accumulator.apply(identity,ele);
        }
        System.out.println(identity);  // 18
    }

其实两种方式背后的思维方式是一样的,那就是:结果重新作为一个参数,不断地参与到运算之中,直到最后结束。
在这里插入图片描述

理解reduce的含义重点就在于理解"累 加 器" 的概念
只要能够理解了累计运算的概念就可以完全理解Stream 中reduce方法,他本质就是一个不断累计运算的过程
在这里插入图片描述

Stream的一个参数和两个参数的方法的基本逻辑都是如此:

  • 对于两个参数的reduce方法来说绿框部分代表的是identity初始值
  • 对于一个参数的reduce方法来说差别是少了绿框中初始值的部分,即result R = T1 ,然后再继续与剩下的元素参与运算。

2,三个参数的reduce

在这里插入图片描述
与两个参数的reduce不同的地方在于类型

  • 双参数的返回类型为T Stream类型为T
  • 三参数的返回类型为U Stream类型为T 有了更大的发挥空间 T可能为U 也可能不是U

类似于:

U result = identity;
for (T element : this stream)
	result = accumulator.apply(result, element)
return result;

演示 T为String类型,U为Int类型:

        // 10+1+3
        Integer r = Arrays.asList("a", "bbb").stream()
                .reduce(10, (x, y) -> x + y.length(), (x, y) -> x + y);
        System.out.println(r); //14

很显然,三参数的reduce 方法的思维方式同双参数的并无二致

接下来重点看下第三个参数:
其实第三个参数用于在并行计算下 合并各个线程的计算结果

并行流运行时:内部使用了fork-join框架

在这里插入图片描述
多线程时,多个线程同时参与运算
多个线程执行任务,必然会产生多个结果
第三个参数的作用是将他们进行正确的合并

大致处理流程:
https://images2018.cnblogs.com/blog/897393/201808/897393-20180821150213047-413187529.png
来看下面的例子:

    @Test
    public void test(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

		//非并行   5+1+2+3+4+5
        Integer reduce1 = list.stream().reduce(5, (x, y) -> x + y, (x, y) -> x + y);
        System.out.println(reduce1); //20

        //并行      5+1 + 5+2 + 5+3 + 5+4 + 5+5
        Integer reduce2 = list.parallelStream().reduce(5, (x, y) -> x + y, (x, y) -> x + y);
        System.out.println(reduce2); //40
    }

对于第一个和第二个结果不同,是由于计算方式不同

  • 非并行流中,12345在一个fork中,初始值与其累加,也就是 5+1+2+3+4+5;
  • 并行流中,将12345分成了5个不同的fork,每个fork分别于初始值进行计算,然后将结果求和,也就是(5+1) + (5+2) + (5+3) + (5+4) + (5+5)。

对于第二个并行流来说,我们想要的应该也是同第一个一样的结果,为什么并行和非并行的结果会有差别呢,是因为我们的写法的问题,我们来看该方法的注释部分:
在这里插入图片描述
在这里插入图片描述

这里是三参数reduce方法中的描述,简单来说就是

  1. 第一点:初始值
    identity 的值对于合并运算combiner来说必须是一个恒等式,也就是说对于任意的u, combiner(identity,u) 和u是相同的。
    这句话看起来怪怪的,对于任意的u 经过合并运算 竟然还是u,那还要这个干嘛??
    从我们上面的并行处理流程可以看得出来,这个result 的初始identity 对于每一个分支都是参与运算的!!!
    这也是为什么要求:任意的u, combiner(identity,u) 和u是相同的原因。

    我们的combiner为 (a,b)->a+b;
    那么如果分为两个分支进行运算,我们的初始值identity就参与了两次运算 也就是说多加了两个identity的值!!
    怎么样才能保证u = combiner(identity,u)
    除非identity=0 这才是对于 (a,b)->a+b 来说能够保障u = combiner(identity,u)
    否则,你就不要用(a,b)->a+b 这个combiner
    我们把Identity换成0之后如下:

        //0+1 + 0+2 + 0+3 + 0+4 + 0+5
        Integer reduce3 = list.parallelStream().reduce(0, (x, y) -> x + y, (x, y) -> x + y);
        System.out.println(reduce3); //15
  1. 第二点:combiner 必须和accumulator要兼容

场景:
假设说4个元素 1,2,3,4 需要运算
此时假设已经 1,2,3 三组数据已经运算结束,马上要同第四组运算
如果是并行,我们假定1,2,3 在一个分支 4单独在另一分支

  • 并行时
    U为已经计算好的1,2,3后的结果 接下来要与另一组的4合并
    T4则是identity与T参与运算,T4的fork中,4会与初始值identity单独进行一次计算
    下面的图就是
    combiner.apply(u, accumulator.apply(identity, t))
    在这里插入图片描述
  • 非并行运算
    u 直接与下一个元素进行结合运算
    在这里插入图片描述
    显然这只是并行和非并行两种不同的处理运算方式,他们应该是相同的
    也就是combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

转载至:https://www.cnblogs.com/noteless/p/9511407.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值