简单的combineByKey算子【看完就懂系列】

代码先行: 

val conf = new SparkConf()
      .setMaster("local")
      .setAppName("CbkDemo")
    val sc = new SparkContext(conf)
    sc.setLogLevel("error")

    val rdd: RDD[(String, Double)] = sc.parallelize(
      Array(("George", 88.0), ("George", 95.0), ("George", 88.0),("KangKang", 93.0),
        ("KangKang", 95.0), ("KangKang", 98.0),("limu", 98.0)))
//    val rdd2: RDD[(String, Double)] = rdd.coalesce(3)
    //求和
    /**
      * createCombiner: V => C ,这个函数把当前的值作为参数,此时我们可以对其做些附加操作(类型转换)并把它返回 (这一步类似于初始化操作)
      * mergeValue: (C, V) => C,该函数把元素V合并到之前的元素C(createCombiner)上 (这个操作在每个分区内进行)
      * mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行)
      */
    val res = rdd.combineByKey(
      x => {println(s"$x******");x},
      (x: Double, y: Double) => {println(s"$x%%%%%%$y");x+y},
      (x: Double, y: Double) => {println(s"$x@@@@@@$y");x+y}
    )
    res.foreach(println)

输出结果:

88.0******
88.0%%%%%%95.0
183.0%%%%%%88.0
93.0******
93.0%%%%%%95.0
188.0%%%%%%98.0
98.0******
(George,271.0)
(KangKang,286.0)
(limu,98.0)

图示:

简单的combineByKey算子【看完就懂系列】_第1张图片

那么怎么走第三部呢?

mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行)
val conf = new SparkConf()
      .setMaster("local")
      .setAppName("CbkDemo")
    val sc = new SparkContext(conf)
    sc.setLogLevel("error")

    val rdd: RDD[(String, Double)] = sc.parallelize(
      Array(("George", 88.0), ("George", 95.0), ("George", 88.0),
        ("George", 88.0), ("George", 95.0), ("George", 88.0),
        ("George", 88.0), ("George", 95.0), ("George", 88.0),
        ("George", 88.0), ("George", 95.0), ("George", 88.0),("KangKang", 93.0),
        ("KangKang", 95.0), ("KangKang", 98.0),("limu", 98.0)),3)
//    val rdd2: RDD[(String, Double)] = rdd.coalesce(3)
    //求和
    /**
      * createCombiner: V => C ,这个函数把当前的值作为参数,此时我们可以对其做些附加操作(类型转换)并把它返回 (这一步类似于初始化操作)
      * mergeValue: (C, V) => C,该函数把元素V合并到之前的元素C(createCombiner)上 (这个操作在每个分区内进行)
      * mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行)
      */
    val res = rdd.combineByKey(
      x => {println(s"$x******");x},
      (x: Double, y: Double) => {println(s"$x%%%%%%$y");x+y},
      (x: Double, y: Double) => {println(s"$x@@@@@@$y");x+y}
    )
    res.foreach(println)

结果展示:

88.0******
88.0%%%%%%95.0
183.0%%%%%%88.0
271.0%%%%%%88.0
359.0%%%%%%95.0
88.0******
88.0%%%%%%88.0
176.0%%%%%%95.0
271.0%%%%%%88.0
359.0%%%%%%88.0
95.0******
95.0%%%%%%88.0
93.0******
93.0%%%%%%95.0
188.0%%%%%%98.0
98.0******
454.0@@@@@@447.0
901.0@@@@@@183.0
(George,1084.0)
(limu,98.0)
(KangKang,286.0)

图示:

简单的combineByKey算子【看完就懂系列】_第2张图片

【总结】

方法的第一个操作在相同分区相同key的时候只操作一次,然后一直进行第二个操作,如果不同分区中有相同的key值则进行第三步操作,否则不执行第三步操作。【因为第二步操作已经把结果算出来了】

友情提示:之所以我们的输出第二步操作时没有输出最终结果,原因在于,为了返回值。我们把输出语句放在了前面,也就是说输出语句后,还有一步加的操作。


 

趁热打铁:

val conf = new SparkConf()
      .setMaster("local")
      .setAppName("CbkDemo")
    val sc = new SparkContext(conf)
    sc.setLogLevel("error")

    val rdd: RDD[(String, Double)] = sc.parallelize(
      Array(("George", 88.0), ("George", 95.0), ("George", 88.0),
        ("George", 88.0), ("George", 95.0), ("George", 88.0),
        ("George", 88.0), ("George", 95.0), ("George", 88.0),
        ("George", 88.0), ("George", 95.0), ("George", 88.0),("KangKang", 93.0),
        ("KangKang", 95.0), ("KangKang", 98.0),("limu", 98.0)),3)
    //求平均数
    val res: RDD[(String, (Int, Double))] = rdd.combineByKey(
      score => (1, score),
      (total: (Int, Double), newScore) => (total._1 + 1, total._2 + newScore),
      (total: (Int, Double), sum: (Int, Double)) => (total._1 + sum._1, total._2 + sum._2)
    )
    val fin: RDD[(String, Double)] = res.map{case (name,(num,score)) => (name,score/num)}
    fin.foreach(println)

输出结果:

(George,90.33333333333333)
(limu,98.0)
(KangKang,95.33333333333333)

再此总结:【谁让它不是很好理解】

combineByKey

是针对不同partition进行操作的。它的第一个参数用于数据初始化(后面着重讲),第二个是针对一个partition的combine操作函数,第三个是在所有partition都combine完毕后,针对所有临时结果进行combine操作的函数。

友情补充:

关于数据初始化

之前有人说,初始化是对每个数据进行操作,这其实是错误的。应该是针对每个partition中,每个key下的第一个数据进行操作。这句话怎么理解呢?看代码:

val rdd1 = sc.parallelize(List(1,2,2,3,3,3,3,4,4,4,4,4), 2)

val rdd2 = rdd1.map((_, 1))

val rdd3 = rdd2.combineByKey(-_, (x:Int, y:Int) => x + y, (x:Int, y:Int) => x + y)

val rdd4 = rdd2.combineByKey(+_, (x:Int, y:Int) => x + y, (x:Int, y:Int) => x + y)

 

rdd2.collect

rdd3.collect

rdd4.collect

 

Array((1,1), (2,1), (2,1), (3,1), (3,1), (3,1), (3,1), (4,1), (4,1), (4,1), (4,1), (4,1))

Array((4,3), (2,0), (1,-1), (3,0))

Array((4,5), (2,2), (1,1), (3,4))  

 

在上述代码中,(1,1), (2,1), (2,1), (3,1), (3,1), (3,1) 被划分到第一个partition,(3,1), (4,1), (4,1), (4,1), (4,1), (4,1) 被划分到第二个。于是有如下操作:

 

(1, 1):由于只有1个,所以在值取负的情况下,自然输出(1, -1)

(2, 1):由于有2个,第一个取负,第二个不变,因此combine后为(2, 0)

(3, 1):partition1中有3个,参照上述规则,combine后为(3, 1),partition2中有1个,因此combine后为(3, -1)。在第二次combine时,不会有初始化操作,因此直接相加,结果为(3, 0)

(4, 1):过程同上,结果为(4, 3)

你可能感兴趣的:(#,bigdata_Spark)