Spark算子学习之五、Key-Vaule算子

2 篇文章 0 订阅
2 篇文章 0 订阅

1.reduceByKey

1)函数签名

def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]

2)功能描述

该函数接收参数中,fun是相同key的value之间两两处理的逻辑,numPartitions结果RDD的分区个数。从入参可以看到,func只涉及到value的处理,没有key,返回结果却是RDD[(K, V)。
该操作可以将RDD[K,V]中的元素按照相同的K对V进行处理。其存在多种重载形式,还可以设置新RDD的分区数。
强调一下,按照相同的key对相同key的value进行聚合,func: (V, V) => V的前提条件是:相同的key已经进行归集,这里只是对相同key的values进行处理,处理步骤:对两个value进行func操作,返回result值,然后拿result和第三个值进行func操作,依次类推,如果相同key只有一个值,就返回他自己。

3)简单案例

    sc.makeRDD(List(("a", 1), ("a", 3), ("a", 5), ("b", 7))).reduceByKey(_ + _, 2)
      .collect().foreach(println)

结果:
(b,7)
(a,9)
简单分析一下执行的流程
1.将key为a的value值进行归集(1,3,5)
2.对key为a的结果集进行func操作:1+3= 4 => 4+5=9
注意在使用或者看到这个算子的时候,一定要注意一种隐含的条件,算子已经将相同的key进行了归集。

2.aggregateByKey

1)函数签名

def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,combOp: (U, U) => U): RDD[(K, U)]

2)功能描述

该函数接收参数比较特殊,使用了柯里化,zeroValue给在每个分区中的每个key给一个初始值(k,v0),然后作为首次参加计算的元素,注意这个v0的类型,既然是作为一个计算元素,说明他的结构或者类型要和RDD的元素保持一致;seqOp分区内相同key的values两两之间处理逻辑;combOp分区内处理后,分区间相同key的values两两之间处理逻辑。
该操作可以将RDD[K,V]中的元素按照相同的K对V进行处理,只是这里的处理分两个步骤,第一步(seqOp)分区内进行处理,第二步(combOp)分区间进行处理。

3)简单案例

案例一、
    sc.makeRDD(List(("a", 1), ("a", 3), ("a", 5), ("b", 7)),2)
            .mapPartitionsWithIndex((index,datas) =>{
              datas.map((index,_))
            }).foreach(println)
    println("-------------------------------------")
    sc.makeRDD(List(("a", 1), ("a", 3), ("a", 5), ("b", 7)),2)
      .aggregateByKey(10)(_+_,_+_)
      .collect().foreach(println)

结果
(0,(a,1))
(1,(a,5))
(0,(a,3))
(1,(b,7))
xxxxxxxxxxxx
(b,17)
(a,29)
那么(a,29)怎么来的呢?
分区内操作:
从分区情况可以看到,0号分区:(0,(a,1))(0,(a,3)),在这个分区会给一个初始值,相当于(a,10),然后(a,10)+(a,1)=(a,11) => (a,11)+(a,3) => (a,14);
1号分区:(1,(a,5)),在这个分区会给一个初始值,相当于(a,10),然后(a,10)+(a,5) => (a,15) ;
分区间操作:
(a,14) + (a,15) =(a,29)

案例二、
    sc.makeRDD(List(("a", 1), ("a", 3), ("a", 5), ("b", 7)), 2)
      .mapPartitionsWithIndex((index, datas) => {
        datas.map((index, _))
      }).foreach(println)
    println("xxxxxxxxxxxx")
    sc.makeRDD(List(("a", 1), ("a", 11), ("a", 3), ("a", 5), ("b", 7)), 2)
      .aggregateByKey((100, 200))(
        (x, y) => (x._1 + 1000, x._2 + y),
        (x, y) => (x._1 + y._1, x._2 + y._2)
      ).collect().foreach(println)

结果
(0,(a,1))
(1,(a,3))
(0,(a,11))
(1,(a,5))
(1,(b,7))
xxxxxxxxxxxx
(b,(1100,207))
(a,(4200,420))
那么(a,(4200,420))怎么来的呢?
先说明一下 (x, y) => (x._1 + 1000, x._2 + y),这里一定要将 (x, y) 看成是一个单值元素,这个函数就是相当于将 一个单值元素转换成了一个元组。
从分区情况可以看到,
0号分区:(0,(a,1)),(0,(a,11)),在这个分区会给一个初始值,相当于(a,(100,200)),
(a,(100,200)) + (a,1) = (a,(100+1000,200+1))=(a,(1100,201))
(a,(1100,201)) +(a,11) = (a,(1100+1000,201+11))=(a,(2100,212))
1号分区:(1,(a,3)),(1,(a,5)),在这个分区会给一个初始值,相当于(a,(100,200)),
(a,(100,200)) + (a,3) = (a,(100+1000,200+3))=(a,(1100,203))
(a,(1100,203)) +(a,5)= (a,(1100+1000,203+5))=(a,(2100,208))
分区间合作:
(a,(2100,212)) + (a,(2100,208)) = (a,(2100+2100,212+208)) =(a,(4200,420))
如果还有问题,那就多看两遍。

3.combineByKey

1)函数签名

  def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C): RDD[(K, C)]

2)功能描述

针对相同K,将V合并成一个集合。
参数解析
(1)createCombiner(转换数据的结构): combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值。
(2)mergeValue(分区内): 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并。
(3)mergeCombiners(分区间): 由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器,就需要使用用户提供的 mergeCombiners()方法将各个分区的结果进行合并。

3)简单案例

sc.makeRDD(List(("a", 1), ("a", 11), ("a", 3), ("a", 5), ("b", 7)), 2)
      .mapPartitionsWithIndex((index, datas) => {
        datas.map((index, _))
      }).foreach(println)
    println("xxxxxxxxxxxx")
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 11), ("a", 3), ("a", 5), ("b", 7)), 2)
    val comRDD = rdd.combineByKey(
      (_, 100),
      (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1000),
      (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
    )
    comRDD.foreach(println)

结果
(1,(a,3))
(0,(a,1))
(1,(a,5))
(0,(a,11))
(1,(b,7))
xxxxxxxxxxxx
(b,(7,100))
(a,(20,2200))
那么(a,(20,2200))怎么来的呢?
从分区情况可以看到,
(acc._1 + v, acc._2 + 1000),
0号分区:(0,(a,1)),(0,(a,11)),假使说处理0号分区数据时第一次遇到(a,1),此时会给一个初始值,相当于(1,100),
(a,(1,100)) + (a,11) = (a,(1+11,100+1000))=(a,(12,1100))
1号分区:(1,(a,3)),(1,(a,5)),假使说处理1号分区数据时第一次遇到(a,3),此时会给一个初始值,相当于(3,100),
(a,(3,100)) + (a,5) = (a,(3+5,100+1000))=(a,(8,1100))
分区间合作:
(a,(12,1100)) + (a,(8,1100)) = (a,(20,2200))
如果还有问题,那就多看两遍。

4.foldByKey

1)函数签名

def foldByKey(zeroValue: V, numPartitions: Int)(func: (V, V) => V): RDD[(K, V)]
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]

2)功能描述

aggregateByKey的简化功能,分区内seqOp和分区间combOp操作相同。

3)简单案例

    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
    rdd.foldByKey(10)(_ + _).foreach(println)

结果
(b,13)
(a,15)
(c,38)
这个结果就不用分析了吧,有问题可以参照aggregateByKey结果解析。

5.groupByKey

1)函数签名

def groupByKey(): RDD[(K, Iterable[V])] = self.withScope {
    //使用父RDD的分区器和分区
    groupByKey(defaultPartitioner(self))
  }
  //指定分区器
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
//指定分区数目,则默认使用hash分区
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])] = self.withScope {
    groupByKey(new HashPartitioner(numPartitions))
 }

2)功能描述

groupByKey对每个key进行操作,但只生成一个seq,并不进行聚合。该操作可以指定分区器或者分区数(默认使用HashPartitioner)。

3)简单案例

sc.makeRDD(List((1,"a"),(1,"a"),(2,"b"),(2,"c"),(1,"d")),2).groupByKey().foreach(println)

结果
(1,CompactBuffer(a, a, d))
(2,CompactBuffer(b, c))
结果很容易能理解,不过要和groupBy做一个区别。

6.sortByKey

1)函数签名

  //默认正序
  def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
      : RDD[(K, V)] = self.withScope
  {
    //使用范围分区,按照key范围将数据分布在不同分区
    val part = new RangePartitioner(numPartitions, self, ascending)
    new ShuffledRDD[K, V, V](self, part)
      .setKeyOrdering(if (ascending) ordering else ordering.reverse)
  }

2)功能描述

在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD。

3)简单案例

    sc.makeRDD(Array((1, "dd"), (1, "aa"), (3, "cc"), (2, "dd"), (1, "cc"), (2, "bb")), 3) /*.collect()*/
      .sortByKey(false)
      .mapPartitionsWithIndex((index, datas) => {
        datas.map((index, _))
      })
      .foreach(println)

结果
(1,(2,dd))
(1,(2,bb))
(0,(3,cc))
(2,(1,dd))
(2,(1,aa))
(2,(1,cc))
从结果可以看到,sortByKey只对key值进行排序,而且是分区内有序。

7.join

1)函数签名

def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))] 

2)功能描述

在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD。

3)简单案例

案例一、
    val rdd1 = sc.makeRDD(Array((1, "aa"), (1, "aa"), (2, "bb")))
    val rdd2 = sc.makeRDD(Array((1, "xx"), (1, "zz")))
    rdd1.join(rdd2).foreach(println)

结果
(1,(aa,xx))
(1,(aa,zz))
(1,(aa,xx))
(1,(aa,zz))
从结果可以看到join和类似于SQL中的inner join,拿着rdd1中的一条数据逐一和rdd2中的数据进行匹配,结果只保留能匹配上的数据。

案例二、
    val rdd1 = sc.makeRDD(Array((1, "aa"), (1, "aa"), (2, "bb")))
    val rdd2 = sc.makeRDD(Array((1, "xx"), (1, "zz")))
    rdd1.leftOuterJoin(rdd2).foreach(println)

结果
(2,(bb,None))
(1,(aa,Some(xx)))
(1,(aa,Some(zz)))
(1,(aa,Some(xx)))
(1,(aa,Some(zz)))
从结果可以看到leftOuterJoin和类似于SQL中的left join,拿着rdd1中的一条数据逐一和rdd2中的数据进行匹配,结果只保留左表存在且能匹配上的数据,如果右表没有匹配的数据,则为none。

案例三、
    val rdd1 = sc.makeRDD(Array((1, "aa"), (1, "aa"), (2, "bb")))
    val rdd2 = sc.makeRDD(Array((1, "xx"), (3, "zz")))
    rdd1.rightOuterJoin(rdd2).foreach(println)

结果
(1,(Some(aa),xx))
(1,(Some(aa),xx))
(3,(None,zz))
从结果可以看到rightOuterJoin和类似于SQL中的right join,拿着rdd1中的一条数据逐一和rdd2中的数据进行匹配,结果只保留右表存在且能匹配上的数据,如果左表没有匹配的数据,则为none。

8.cogroup

1)函数签名

  def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
    cogroup(other, defaultPartitioner(self, other))
  }
  def cogroup[W](
      other: RDD[(K, W)],
      numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
    cogroup(other, new HashPartitioner(numPartitions))
  }

2)功能描述

在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD,rdd内部先将相同key的value归集到一个列表中,然后rdd之间将相同key的两个列表合并。

3)简单案例

        val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"a"),(2,"b"),(3,"c"),(2,"b"),(3,"c")))
        val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1,4),(2,5),(3,6),(5,7)))
        rdd.cogroup(rdd1).collect().foreach(println)

结果
(1,(CompactBuffer(a),CompactBuffer(4)))
(5,(CompactBuffer(),CompactBuffer(7)))
(2,(CompactBuffer(b, b),CompactBuffer(5)))
(3,(CompactBuffer(c, c),CompactBuffer(6)))

9.mapValues

1)函数签名

  def mapValues[U](f: V => U): RDD[(K, U)] = self.withScope {
    val cleanF = self.context.clean(f)
    new MapPartitionsRDD[(K, U), (K, V)](self,
      (context, pid, iter) => iter.map { case (k, v) => (k, cleanF(v)) },
      preservesPartitioning = true)
  }

2)功能描述

针对于(K,V)形式的类型只对V进行操作,原来的key不变。代码实现case (k, v) => (k, cleanF(v)),只对value进行操作。

3)简单案例

val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"aa"),(3,"cc"),(2,"bb"),(4,"dd")))
rdd.mapValues(_+"xxxx").foreach(println)

结果
(4,ddxxxx)
(3,ccxxxx)
(2,bbxxxx)
(1,aaxxxx)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值