Spark Core学习之常用算子(含经典面试题)

目录

前言

第一部分:Transformation算子

Value类型

map()映射

mapPartitions()以分区单位执行Map

mapPartitionsWithIndex()带分区号

flatMap()压平

glom()分区转换数组

groupBy()分组

扩展:复杂版的wordcount

filter()过滤

sample()采样

distinct()去重

coalesce()重新分区

reparation()重新分区(执行Shuffle)

sortBy()排序

pipe()调用脚本

双Value类型交互

union()并集、subtract()差集、intersection()交集

 zip()拉链

Key-Value类型

partitionBy()按照k重新分区

reduceByKey()按照k进行聚合v

备注: 正则表达式过滤字符串的方法

groupByKey()按照k重新分组

aggregateByKey()按照k进行分区内和分区间逻辑

foldByKey()分区内核分区间具有相同逻辑的aggregateByKey()

combineByKey()转换结构后分区内和分区间操作

sortByKey()按照k进行排序

mapValues()只对v进行操作

join()将相同k对应的多个v关联在一起

扩展:左外连接、右外连接和全外连接

cogroup()类似全连接,但是在同一个RDD中对k聚合

第二部分:Action行动算子

reduce()聚合

collect()以数组的形式返回数据集

count()返回RDD中元素个数

first()返回RDD中的第一个元素

take(n)返回由RDD前n个元素组成的数组

takeOrdered(n)返回RDD排序后前n个元素组成的数组

aggregate()案例

fold()案例

countByKey()统计每种key的个数

save相关算子

foreach()&foreachPartition遍历RDD中每一个元素

总结


前言

        在 Spark Core中,RDD(Resilient Distributed Dataset,弹性分布式数据集) 支持 2 种操作:

1、transformation
        从一个已知的 RDD 中创建出来一个新的 RDD 。例如: map就是一个transformation。
2、action
        在数据集上计算结束之后, 给驱动程序返回一个值.。例如: reduce就是一个action。

        注意:

                本文只简单讲述Transformation算子和Action算子,目的是了解其相应的算子如何使用;并不打算深入理解每一个算子的执行过程和逻辑思想。


        在 Spark 中几乎所有的transformation操作都是懒执行的(lazy),也就是说transformation操作并不会立即计算他们的结果,而是记住了这个操作。只有当通过一个action来获取结果返回给驱动程序的时候这些转换操作才开始计算。这种设计可以使 Spark 运行起来更加的高效


        默认情况下,你每次在一个 RDD 上运行一个action的时候,前面的每个transformed RDD 都会被重新计算。但是我们可以通过persist (or cache)方法来持久化一个 RDD 在内存中,也可以持久化到磁盘上, 来加快访问速度。

 根据 RDD 中数据类型的不同,整体分为 2 种 RDD:

  • Value类型
  • Key-Value类型(其实就是存了一个二维的元组)

第一部分:Transformation算子

Value类型

map()映射

需求:

创建一个1~4数组的RDD,两个分区,将所有元素*2形成新的RDD。

        代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    mapTest(sc)

    //4.关闭连接
    sc.stop()
  }

  def mapTest(sc: SparkContext): Unit = {
    // 3.1 创建一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)

    // 3.2 调用map方法,每个元素乘以2
    val mapRdd: RDD[Int] = rdd.map(_ * 2)

    // 3.3 打印修改后的RDD中数据
    mapRdd.collect().foreach(println)
  }
}

         map()操作如图所示:

map()函数结构 
  def map[U: ClassTag](f: T => U): RDD[U] = withScope {
    val cleanF = sc.clean(f)
    new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF))
  }

功能描述

        参数f是一个函数,它可以接收一个参数。当某个RDD执行map方法时,会遍历该RDD中的每一个数据项,并依次应用f函数,从而产生一个新的RDD。即,这个新RDD中的每一个元素都是原来RDD中每一个元素依次应用f函数而得到的。

        在本例中,f为:_*2

        备注:rdd.map(_ * 2)是rdd.map((f: Int) => f * 2)的简写。

mapPartitions()以分区单位执行Map

需求:

创建一个RDD,4个元素,2个分区,使每个元素*2组成新的RDD

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    mapPartitionsTest(sc)

    //4.关闭连接
    sc.stop()
  }

  /**
   * 创建一个RDD,4个元素,2个分区,使每个元素*2组成新的RDD
   * */
  def mapPartitionsTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)

    // 3.2 需求实现
    val value: RDD[Int] = rdd.mapPartitions((data: Iterator[Int]) => data.map((x: Int) => x * 2))

    // 3.3 打印
    value.foreach(println)
  }
}

mapPartitions()操作如图所示:

 mapPartitions()函数结构:

def mapPartitions[U: ClassTag](
      f: Iterator[T] => Iterator[U],
      preservesPartitioning: Boolean = false
): RDD[U] = withScope {
    val cleanedF = sc.clean(f)
    new MapPartitionsRDD(
      this,
      (context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(iter),
      preservesPartitioning)
  }

功能介绍:

  • f函数把每一个分区的数据分别放入到迭代器中,批处理。
  • preservesPartitioning:是否保留上游RDD的分区信息,默认false
  • Map是一次处理一个元素,而mapPartitions一次处理一个分区数据。

备注:map()和mapPartitions()的区别

  1. map():每次处理一条数据。
  2. mapPartitions():每次处理一个分区的数据,这个分区的数据处理完后,原 RDD 中该分区的数据才能释放,可能导致 OOM。
  3. 开发指导:当内存空间较大的时候建议使用mapPartitions(),以提高处理效率。

mapPartitionsWithIndex()带分区号

需求:

创建一个RDD,使每个元素跟所在分区号形成一个元组,组成一个新的RDD。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    mapPartitionsWithIndexTest(sc)
    //4.关闭连接
    sc.stop()
  }

  /**
   * 创建一个RDD,使每个元素跟所在分区号形成一个元组,组成一个新的RDD
   * */
  def mapPartitionsWithIndexTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)

    // 3.2 需求实现
    val value: RDD[(Int, Int)] = rdd.mapPartitionsWithIndex((index: Int, item: Iterator[Int]) => {
      item.map((f: Int) => (index,f))
    })
    // 3.3 打印
    value.foreach(println)
  }
}

mapPartitionsWithIndex()操作如图所示:

 mapPartitionsWithIndex()函数结构:

def mapPartitionsWithIndex[U: ClassTag](
      f: (Int, Iterator[T]) => Iterator[U],
      preservesPartitioning: Boolean = false): RDD[U] = withScope {
    val cleanedF = sc.clean(f)
    new MapPartitionsRDD(
      this,
      (context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(index, iter),
      preservesPartitioning)
  }

功能介绍:

  1.  f: (Int, Iterator[T]) => Iterator[U]中Int表示分区编号
  2. 类似于mapPartitions,比mapPartitions多一个整数参数表示分区号

flatMap()压平

需求:

创建一个集合,集合里面存储的还是子集合,把所有子集合中数据取出放入到一个大的集合中。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    flatMapTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 创建一个集合,集合里面存储的还是子集合,把所有子集合中数据取出放入到一个大的集合中
   * */
  def flatMapTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd: RDD[List[Int]] = sc.makeRDD(Array(List(1,2),List(3,4),List(5,6),List(7)),2)

    // 3.2 需求实现
    val value: RDD[Int] = rdd.flatMap((list: List[Int]) => list)

    // 3.3 打印
    value.foreach(println)
  }
}

flatMap操作如图所示:

扩展: 可以通过任务上下文获取分区号。

 rdd.foreach((f: List[Int]) => {
      // 通过任务上下文获取partitionID
      println(
TaskContext.getPartitionId() + "---"+ f.mkString(","))

    })

flatMap()函数结构:

def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U] = withScope {
    val cleanF = sc.clean(f)
    new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.flatMap(cleanF))
  }

功能介绍:

  1. 与map操作类似,将RDD中的每一个元素通过应用f函数依次转换为新的元素,并封装到RDD中。
  2. 区别:在flatMap操作中,f函数的返回值是一个集合,并且会将每一个该集合中的元素拆分出来放到新的RDD中。并且新的RDD中继承了原RDD中的分区数。 

glom()分区转换数组

需求:

创建一个2个分区的RDD,并将每个分区的数据放到一个数组,求出每个分区的最大值。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    glomTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 创建一个2个分区的RDD,并将每个分区的数据放到一个数组,求出每个分区的最大值
   * */
  def glomTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)
    // 3.2 需求实现
    val value: RDD[Int] = rdd.glom().mapPartitions((x: Iterator[Array[Int]]) => x.map((f: Array[Int]) => f.max))

    // 3.3 打印
    value.foreach((f: Int) => {
      println(TaskContext.getPartitionId() + ":" + f)
    })
  }
}

glom()操作如图所示:

glom()函数结构:

 def glom(): RDD[Array[T]] = withScope {
    new MapPartitionsRDD[Array[T], T](this, (context, pid, iter) => Iterator(iter.toArray))
  }

功能介绍:

        该操作将RDD中每一个分区变成一个数组,并放置在新的RDD中,数组中元素的类型与原分区中元素类型一致。

groupBy()分组

需求:

创建一个RDD,按照元素模以2的值进行分组。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    groupByTest(sc)
    //4.关闭连接
    sc.stop()
  }

  /**
   * 创建一个RDD,按照元素模以2的值进行分组
   * */
  def groupByTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 10,3)
    // 3.2 需求实现
    val value: RDD[(Int, Iterable[Int])] = rdd.groupBy(_ % 2)
    // 3.3 打印
        value.foreach(println)

  }
}

groupBy()操作如图所示:

groupBy()函数结构:

  def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])] = withScope {
    groupBy[K](f, defaultPartitioner(this))
  }

功能介绍:

分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。

备注:

1、groupBy会存在shuffle过程
2、shuffle:将不同的分区数据进行打乱重组的过程
3、shuffle一定会落盘。

扩展:复杂版的wordcount

需求:

        有如下的数据,("Hello Scala", 2), ("Hello Spark", 3), ("Hello World", 2),("I Love You",5),("I Miss You",2),("Best wish",9)。其中数字代表出现的个数。求每个单词出现的次数。

代码实现:

def worldCountTest(sc: SparkContext): Unit ={
    val rdd: RDD[(String, Int)] = sc.makeRDD(Array(("Hello Scala", 2), ("Hello Spark", 3), ("Hello World", 2), ("I Love You", 5), ("I Miss You", 2), ("Best wish", 9)))

    // 方式一:适合scala
    /*val value: String = rdd.map {
          // 模式匹配
      case (str, count) => {
        // scala对字符串操作,("Hello Scala" + " ") * 2 = Hello Scala Hello Scala
        (str + " ") * count
      }
    }
      .flatMap(_.split(" "))
      .map((_, 1))
      .reduceByKey(_+_)
      .collect()
      .mkString(",")
*/

    // 方式二:比较通用
    val value: String = rdd.flatMap {
      case (str, i) => {
        str.split(" ").map((word: String) => (word, i))
      }
    }.reduceByKey(_ + _)
      .collect()
      .mkString(",")

    println(value)

  }

filter()过滤

需求:

创建一个1到10的RDD,过滤出偶数。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    filterTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 过滤偶数
   * */
  def filterTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 10, 3)
    // 3.2 需求实现
    val value: RDD[Int] = rdd.filter(_ % 2 == 0)
    // 3.3 打印
    value.foreach(println)
  }
}

filter()操作过程如下:

filter()函数结构:

  def filter(f: T => Boolean): RDD[T] = withScope {
    val cleanF = sc.clean(f)
    new MapPartitionsRDD[T, T](
      this,
      (context, pid, iter) => iter.filter(cleanF),
      preservesPartitioning = true)
  }

功能介绍:

  • 接收一个返回值为布尔类型的函数作为参数。
  • 当某个RDD调用filter方法时,会对该RDD中每一个元素应用f函数,如果返回值类型为true,则该元素会被添加到新的RDD中。

sample()采样

需求:

创建一个RDD(1-10),从中选择放回和不放回抽样。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    sampleTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 随机采样
   * */
  def sampleTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 20, 3)
    // 3.2 需求实现
    val value: String = rdd.sample(true,0.3,3).collect().mkString(",")
    val value2: String = rdd.sample(false,0.3,3).collect().mkString(",")
    // 3.3 打印
    
    println("不放回采样结果:" + value2)
    println("放回采样结果:" + value)
  }
}

sample()操作如图所示:

sample()函数结构:

def sample(
      withReplacement: Boolean,
      fraction: Double,
      seed: Long = Utils.random.nextLong
): RDD[T] = {
    ......
  }

功能介绍:

  • 从大量的数据中采样
  • withReplacement: Boolean: 抽出的数据是否放回
  • fraction: Double:
    • 当withReplacement=false时:选择每个元素的概率;取值一定是[0,1] ;底层使用泊松分布。
    • 当withReplacement=true时:选择每个元素的期望次数; 取值必须大于等于0,底层使用伯努利采样。
  • seed: Long: 指定随机数生成器种子

备注:

       1、 该函数随机采样是伪随机,因为传入的随机种子是一样的,计算结果当然一样。

distinct()去重

需求:

对如下的数据去重:3,2,9,1,2,1,5,2,9,6,1

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    distinctTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 数据去重
   * */
  def distinctTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(List(3,2,9,1,2,1,5,2,9,6,1))
    // 3.2 需求实现
    val value: RDD[Int] = rdd.distinct()
    // 3.3 打印
    value.foreach(println)
  }
}

distinct()操作如图所示:

distinct()函数结构:

  def distinct(): RDD[T] = withScope {
    distinct(partitions.length)
  }

  def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
    map(x => (x, null)).reduceByKey((x, y) => x, numPartitions).map(_._1)
  }

功能介绍:

对内部的元素去重,并将去重后的元素放到新的RDD中。

备注:

        1、distinct()用分布式的方式去重比HashSet集合方式不容易OOM.

        2、默认情况下,distinct会生成与原RDD分区个数一致的分区数,当然也可以指定分区数。

coalesce()重新分区

需求:

1、将4个分区的RDD合并成两个分区的RDD

2、将三个分区的RDD合并成两个分区的RDD

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    coalesceTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * coalesce重分区
   * */
  def coalesceTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 4,4)
    val rdd2: RDD[Int] = sc.makeRDD(1 to 4,3)
    // 3.2 需求实现
    val value1: Array[(Int, Int)] = rdd.coalesce(2).map((TaskContext.getPartitionId(),_)).collect()
    val value2: Array[(Int, Int)] = rdd2.coalesce(2).map((TaskContext.getPartitionId(),_)).collect()
    // 3.3 打印
    println("value1:" + value1.mkString(","))
    println("value2:" + value2.mkString(","))
  }
}

coalesce()操作如图所示:

coalesce()函数结构:

def coalesce(numPartitions: Int, shuffle: Boolean = false,
               partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
              (implicit ord: Ordering[T] = null)

      : RDD[T] = withScope {

......

}

功能介绍:

  • 缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。
  • shuffle默认为false,则该操作会将分区数较多的原始RDD向分区数比较少的目标RDD,进行转换。
  • shuffle:
    • true: 进行shuffle,此时目标分区数可以大于可以小于原分区数。即:可以缩小和扩大原分区。
    • false:不进行shuffle,此时目标分区数只能小于原分区数;当大于时,分区数不生效。即:只能缩小或等于原分区。

备注:

        1、shuffle原理: 将数据打散,然后重新组合。

        2、具体shuffle过程,读者可以参考“彻底搞懂spark的shuffle过程”。

reparation()重新分区(执行Shuffle)

需求:

创建一个4个分区的RDD,对其重新分区

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    repartitionTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * repartition重分区
   * */
  def repartitionTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 10,4)

    // 3.2 需求实现
    val value1: Array[(Int, Int)] = rdd.repartition(2).map((TaskContext.getPartitionId(),_)).collect()
    val value2: Array[(Int, Int)] = rdd.repartition(5).map((TaskContext.getPartitionId(),_)).collect()

    // 3.3 打印
    println("value1:" + value1.mkString(","))
    println("value2:" + value2.mkString(","))
  }
}

reparation()操作如图所示:

reparation()函数结构:

  def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
    coalesce(numPartitions, shuffle = true)
  }

功能介绍:

  • 该操作内部其实执行的是coalesce操作,参数shuffle的默认值为true。
  • 无论是将分区数多的RDD转换为分区数少的RDD,还是将分区数少的RDD转换为分区数多的RDD,repartition操作都可以完成,因为无论如何都会经shuffle过程。

备注: coalesce与reparation的区别

  • coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定。
  • repartition实际上是调用的coalesce,进行shuffle。
  • 如果是减少分区, 尽量避免 shuffle,使用coalesce完成。
  • 绝大多数情况下,减少分区使用coalesce,增加分区使用reparation。

sortBy()排序

需求:

创建一个RDD,按照不同的规则进行排序。

  1. 按照数字大小升序排序
  2. 按照数字大小降序排序
  3. 按照模以5的余数降序排序
  4. 二元组,则先按照第一个元素升序,如果第一个元素相同,在按照第二个元素降序

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    sortByTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * sortBy排序
   * */
  def sortByTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[Int] = sc.makeRDD(List(3,16,5,8,2,10,9,1,7,6))

    // 3.2 需求实现
    // 升序
    val value1: String = rdd.sortBy((num: Int) => num).collect().mkString(",")
    // 降序
    val value2: String = rdd.sortBy((num: Int) => num,false).collect().mkString(",")
    // 按照%5排序
    val value3: String = rdd.sortBy((num: Int) => num % 5,false).collect().mkString(",")

    // 先按照第一个元素升序,如果第一个元素相同,在按照第二个元素降序
    val value4: String = rdd2.sortBy((x: (Int, Int)) => (x._1,-x._2)).collect().mkString(",")

    // 3.3 打印
    println("value1:" + value1)
    println("value2:" + value2)
    println("value3:" + value3)
    println("value4:" + value4)
  }
}

sortBy()操作如图所示:

sortBy()函数结构:

  def sortBy[K](
      f: (T) => K,
      ascending: Boolean = true,
      numPartitions: Int = this.partitions.length
)
      (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] = withScope {
    this.keyBy[K](f)
        .sortByKey(ascending, numPartitions)
        .values
  }

功能介绍:

  • 该操作用于排序数据。
  • 在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理的结果进行排序,默认为正序排列。
  • 默认情况下,排序后新产生的RDD的分区数与原RDD的分区数一致。
  • 可以通过numPartitions参数调整目标分区数。

pipe()调用脚本

需求:

编写一个脚本,使用管道将脚本作用于RDD上。

代码实现:

# 编写一个脚本,并增加执行权限
[root@node001 spark]$ vim pipe.sh
#!/bin/sh
echo "Start"
while read LINE; do
   echo ">>>"${LINE}
done

[root@node001 spark]$ chmod 777 pipe.sh

# 创建一个只有一个分区的RDD
scala> val rdd = sc.makeRDD (List("hi","Hello","how","are","you"),1)

# 将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()
res18: Array[String] = Array(Start, >>>hi, >>>Hello, >>>how, >>>are, >>>you)

#创建一个有两个分区的RDD
scala> val rdd = sc.makeRDD(List("hi","Hello","how","are","you"),2)

# 将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()
res19: Array[String] = Array(Start, >>>hi, >>>Hello, Start, >>>how, >>>are, >>>you)

pipe()操作如图所示:

pipe()函数结构:

def pipe(command: String): RDD[String] = withScope {
    // Similar to Runtime.exec(), if we are given a single string, split it into words
    // using a standard StringTokenizer (i.e. by spaces)
    pipe(PipedRDD.tokenize(command))
  }

功能介绍:

  • 管道,针对每个分区,都调用一次shell脚本,返回输出的RDD
  • 脚本要放在 worker 节点可以访问到的位置

  • 每个分区执行一次脚本, 但是每个元素算是标准输入中的一行

双Value类型交互

        双Value类型的交互常用的算子是数学中的并集、交集合差集。

union()并集、subtract()差集、intersection()交集

需求:

创建两个RDD,求并集、交集、差集

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    aggTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 求并集、交集、差集
   * */
  def aggTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd1: RDD[Int] = sc.makeRDD (1 to 6)
    val rdd2: RDD[Int] = sc.makeRDD (4 to 10)

    // 3.2 需求实现
    // 并集
    val value1: Array[Int] = rdd1.union(rdd2).collect()
    // 差集
    val value2: Array[Int] = rdd1.subtract(rdd2).collect()
    // 交集
    val value3: Array[Int] = rdd1.intersection(rdd2).collect()

    // 3.3 打印
    println("并集:" + value1.mkString(","))
    println("差集:" + value2.mkString(","))
    println("交集:" + value3.mkString(","))
  }
}

union()并集操作如图所示:

subtract()差集操作如图所示:

intersection()交集操作如图所示:

union()函数结构:

  def union(other: RDD[T]): RDD[T] = withScope {
    sc.union(this, other)
  }

subtract()函数结构:

 def subtract(other: RDD[T]): RDD[T] = withScope {
    subtract(other, partitioner.getOrElse(new HashPartitioner(partitions.length)))
  }

intersection()函数结构:

  def intersection(other: RDD[T]): RDD[T] = withScope {
    this.map(v => (v, null)).cogroup(other.map(v => (v, null)))
        .filter { case (_, (leftGroup, rightGroup)) => leftGroup.nonEmpty && rightGroup.nonEmpty }
        .keys
  }

功能介绍:

  • 并集:对源RDD和参数RDD求并集后返回一个新的RDD
  • 差集:计算差的一种函数,去除两个RDD中相同元素,不同的RDD将保留下来
  • 交集:对源RDD和参数RDD求交集后返回一个新的RDD

 zip()拉链

需求:

创建两个RDD,并将两个RDD组合到一起形成一个(k,v)RDD

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    zipTest(sc)
    
    //4.关闭连接
    sc.stop()
  }
  /**
   * zip拉链
   * */
  def zipTest(sc: SparkContext): Unit ={
    // 3.1 创建第一个RDD
    val rdd1: RDD[Int] = sc.makeRDD (1 to 3,2)
    val rdd2: RDD[String] = sc.makeRDD (List("a","b","c"),2)

    // 3.2 需求实现
    val value: Array[(String, Int)] = rdd2.zip(rdd1).collect()

    // 3.3 打印
    println(value.mkString(","))
  }
}

zip()操作如图所示:

zip()函数结构:

def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)] = withScope {

......

}

功能介绍:

  • 该操作可以将两个RDD中的元素,以键值对的形式进行合并。其中,键值对中的Key为第1个RDD中的元素,Value为第2个RDD中的元素。
  • 将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。

Key-Value类型

partitionBy()按照k重新分区

需求:

创建一个5个分区的RDD,对其重新分区。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    partitionByKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * partitionByKey:根据key重新分区
   * */
  def partitionByKeyTest(sc: SparkContext): Unit ={
    val tuples: List[(Int, String)] = List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc"), (4, "dd")
      ,(1,"aaa"),(2,"bbb"),(3,"ccc"),(4,"ddd"))
    // 3.1 创建第一个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD (tuples,5)
    rdd.cache()

    println("-------分区前-------")
    val value1: String = rdd.mapPartitionsWithIndex((index: Int, data: Iterator[(Int, String)]) => {
      data.map((index,_))
    }).collect().mkString("\t")

    println(value1)
    // 3.2 需求实现
    val value: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(3))

    // 3.3 打印
    println("-------分区后-------")
    val value2: String = value.mapPartitionsWithIndex((index: Int, data: Iterator[(Int, String)]) => {
      data.map((index,_))
    }).collect().mkString("\t")
    println(value2)
  }
}

partitionBy()操作如图所示:

partitionBy()函数结构:

def partitionBy(partitioner: Partitioner): RDD[(K, V)] = self.withScope {
    if (keyClass.isArray && partitioner.isInstanceOf[HashPartitioner]) {
      throw new SparkException("HashPartitioner cannot partition array keys.")
    }
    if (self.partitioner == Some(partitioner)) {
      self
    } else {
      new ShuffledRDD[K, V, V](self, partitioner)
    }
  }

功能介绍:

  • 将RDD[K,V]中的K按照指定Partitioner重新进行分区;会产生Shuffle过程。
  • 如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区,否则进行分区。
  • 默认的分区器是HashPartitioner。

备注:

        1、HashPartitioner的原理是:

  • 依据RDD中key值的hashCode的值将数据取模后得到该key值对应的下一个RDD的分区id值,
  • 支持key值为null的情况,当key为null的时候,返回0;
  • 该分区器基本上适合所有RDD数据类型的数据进行分区操作。

        2、详细的分区器原理,请参考:[Spark] - HashPartitioner & RangePartitioner 区别

注意:

        1、由于JAVA中数组的hashCode是基于数组对象本身的,不是基于数组内容的,所以如果RDD的key是数组类型,那么可能导致数据内容一致的数据key没法分配到同一个RDD分区中。(未测)

        2、在scala中,如果RDD的key是Array数组类型,编译不通过。Exception in thread "main" org.apache.spark.SparkException: HashPartitioner cannot partition array keys.

     这个时候最好自定义数据分区器,采用数组内容进行分区或者将数组的内容转换为集合。

自定义分区

class MyPartitioner(num: Int) extends Partitioner {
    // 设置的分区数
    override def numPartitions: Int = num

    // 具体分区逻辑
    override def getPartition(key: Any): Int = {

        if (key.isInstanceOf[Int]) {

            val keyInt: Int = key.asInstanceOf[Int]
            if (keyInt % 2 == 0)
                0
            else
                1
        }else{
            0
        }
    }
}

reduceByKey()按照k进行聚合v

需求:

统计单词出现次数(wordCount)。

代码实现:

为了增加难度,本次实验给字符串加了一些中英文的字符。

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    reduceByKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }


  def reduceByKeyTest(sc: SparkContext): Unit = {
    val tuple1: String = "Apache Spark™ is a: multi-language engine for executing★ data engineering, "
    val tuple2: String = "$Apache Spark™ is a multi-language ※engine for executing data ¥engineering, "
    val tuple3: String = "Apache Spark™ is (a multi-language) engine for executing data engineering, "

    val tuples: String = tuple1 + tuple2 + tuple3
    // 忽略标点字符
    val str: String = tuples.replaceAll("\\pP|\\pS", "")
    // 3.1 创建第一个RDD
    val rdd: RDD[String] = sc.parallelize(Array(str))
    // 3.2 需求实现
    val value2: String = rdd.flatMap(f => f.split(" "))
      .map((_, 1))
      .reduceByKey(_ + _)
      .collect()
      .mkString("\n")

    // 3.3 打印

    println(value2)
  }
}

 运行结果:

备注: 正则表达式过滤字符串的方法

/pP :其中的小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。大写 P 表示 Unicode 字符集七个字符属性之一:标点字符。

其他六个是:

L:字母;
M:标记符号(一般不会单独出现);
Z:分隔符(比如空格、换行等);
S:符号(比如数学符号、货币符号等);
N:数字(比如阿拉伯数字、罗马数字等);
C:其他字符

reduceByKey()操作如图:

reduceByKey()函数结构:

  def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)] = self.withScope {
    combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
  }

def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)] = self.withScope {
    reduceByKey(new HashPartitioner(numPartitions), func)
  }

def reduceByKey(func: (V, V) => V): RDD[(K, V)] = self.withScope {
    reduceByKey(defaultPartitioner(self), func)
  }

功能介绍:

  • 该操作可以将RDD[K,V]中的元素按照相同的K对V进行聚合。
  • 其存在多种重载形式,还可以设置新RDD的分区数。
  • 默认的分区器为HashPartitioner

groupByKey()按照k重新分组

需求:

求List(("a",1),("b",5),("a",5),("b",2),("a",1),("c",5),("b",5),("b",2))中abc平均出现的次数。

函数实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    groupByKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }

  /**
   * groupByKey():按照key分组
   * */
  def groupByKeyTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD

    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("c",15),("a",5),("b",2),("a",1),("c",9),("b",5),("b",2)), 2)

    // 3.2 需求实现
    val value: Array[(String, String)] = rdd.groupByKey().map(f => {
      val format: DecimalFormat = new DecimalFormat("#0.00")
      val sum: Int = f._2.sum
      val count: Int = f._2.size
      val nums: Double = sum.toDouble / count
      (f._1,format.format(nums))
    }).collect()

    // 3.3 打印
    println(value.mkString("\n"))
  }
}

备注: DecimalFormat 类。

        该类主要靠 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。

如:#.00表示小数点后保留两位,小数点前不确定,有几位都行。

        00.00表示整数部分两位,不足用0补足;小数部分2位,不足用0补足。

groupByKey()操作如图:

groupByKey()函数结构:

  def groupByKey(): RDD[(K, Iterable[V])] = self.withScope {
    groupByKey(defaultPartitioner(self))
  }

def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])] = self.withScope {

}

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

功能介绍:

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

 备注: reduceByKey和groupByKey区别

  1. reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。
  2. groupByKey:按照key进行分组,直接进行shuffle。
  3. 开发指导:
    1. 在不影响业务逻辑的前提下,优先选用reduceByKey。
    2. 求和操作不影响业务逻辑,求平均值影响业务逻辑。

aggregateByKey()按照k进行分区内和分区间逻辑

需求

创建一个RDD,取出RDD中每个分区相同key对应值的最大值,然后相加。

        代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    aggregateByKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }

  /**
   * 取出每个分区相同key对应值的最大值,然后相加
   * */
  def aggregateByKeyTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val array: Array[(String, Int)] = Array(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8))
    val rdd: RDD[(String, Int)] = sc.makeRDD(array, 2)

    // 3.2 取出每个分区相同key对应值的最大值,然后相加
    //    val value: RDD[(String, Int)] = rdd.aggregateByKey(0)(math.max, _ + _)
    // 不使用math函数
    val value: RDD[(String, Int)] = rdd.aggregateByKey(0)((a: Int, b: Int) => {
      if (a > b) a else b
    }, _ + _)

    // 3.3 打印结果
    value.foreach(println)
  }

        aggregateByKey()操作如图所示:

函数结构 :

 

  def aggregateByKey[U: ClassTag](zeroValue: U, partitioner: Partitioner)(

        seqOp: (U, V) => U,
        combOp: (U, U) => U): RDD[(K, U)] = self.withScope {
    *********
  }

功能介绍:

(1)zeroValue(初始值):给每一个分区中的每一种key一个初始值;
(2)seqOp(分区内):函数用于在每一个分区中用初始值逐步迭代value;
(3)combOp(分区间):函数用于合并每个分区中的结果。

foldByKey()分区内核分区间具有相同逻辑的aggregateByKey()

需求:

将上例中的数据求一个worldcount。

        代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    foldByKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }

  def foldByKeyTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val array: Array[(String, Int)] = Array(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8))
    val rdd: RDD[(String, Int)] = sc.makeRDD(array)

    // 3.2 worldcount
    //    val value: RDD[(String, Int)] = rdd.aggregateByKey(0)(_+_, _ + _)
    val value: RDD[(String, Int)] = rdd.foldByKey(0)(_ + _)


    // 3.3 打印
    value.foreach(println)
  }

foldByKey()操作如图所示:

 foldByKey()函数结构:

def foldByKey(
      zeroValue: V,
      partitioner: Partitioner
)(func: (V, V) => V): RDD[(K, V)] = self.withScope {

........

}

功能介绍:

  1. aggregateByKey的简化操作,seqop和combop相同。即,分区内逻辑和分区间逻辑相同。
  2. 参数zeroValue:是一个初始化值,它可以是任意类型

  3. 参数func:是一个函数,两个输入参数相同

combineByKey()转换结构后分区内和分区间操作

需求:

创建一个pairRDD,根据key计算每种key的均值。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    combineKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }


  def combineKeyTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)))

    // 3.2 需求实现: 将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组
    val value: Array[(String, String)] = rdd.combineByKey(
      (_, 1),
      (acc: (Int, Int), other: Int) => (acc._1 + other, acc._2 + 1), // 注意此处的类型必须手动添加,否则无法使用
      (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
    ).map(f => {
      val format: DecimalFormat = new DecimalFormat("#0.00")
      (f._1, format.format(f._2._1 / f._2._2.toDouble))
    }).collect()


    // 3.3 打印
    println(value.mkString("\n"))
  }
}

combineByKey()操作如图所示:

combineByKey()函数功能:

def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      partitioner: Partitioner,
      mapSideCombine: Boolean = true,
      serializer: Serializer = null
): RDD[(K, C)] = self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners,
      partitioner, mapSideCombine, serializer)(null)
  }

  def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      numPartitions: Int): RDD[(K, C)]
= self.withScope {
    combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, numPartitions)(null)
  }

功能介绍:

  • createCombiner:V=>C 分组内的创建组合的函数。通俗点将就是对读进来的数据进行初始化,其把当前的值作为参数,可以对该值做一些转换操作,转换为我们想要的数据格式
  • mergeValue: (C, V) => C该函数主要是分区内的合并函数,作用在每一个分区内部。其功能主要是将V合并到之前(createCombiner)的元素C上,注意,这里的C指的是上一函数转换之后的数据格式,而这里的V指的是原始数据格式(上一函数为转换之前的)
  • mergeCombiners:(C,C)=>R 该函数主要是进行多分区合并,此时是将两个C合并为一个C,例如两个C:(Int)进行相加之后得到一个R:(Int)

备注: reduceByKey、aggregateByKey、foldByKey、combineByKey 四种对key聚合的区别和联系

  1. 四种底层都调用了combineByKeyWithClassTag
  2. reduceByKey 没有初始值,分区内和分区间逻辑相同
  3. aggregateByKey 第一个初始值和分区内处理规则一致,分区内和分区间逻辑可以不同
  4. foldByKey 有初始值,分区内和分区间逻辑一致

  5. combineByKey 初始值可以变化结构,分区内和分区间逻辑不同

sortByKey()按照k进行排序

需求:

创建一个pairRDD,按照key的正序和倒序进行排序

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    sortByKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * sortByKeyTest():在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
   * */
  def sortByKeyTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)))

    // 3.2 需求实现
    val value1: Array[(String, Int)] = rdd.sortByKey().collect()
    val value2: Array[(String, Int)] = rdd.sortByKey(false).collect()


    // 3.3 打印
    println(value1.mkString("\n"))
    println("--------------------------")
    println(value2.mkString("\n"))
  }
}

sortByKey()操作如图所示:

sortByKey()函数结构:

def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
      : RDD[(K, V)] = self.withScope
  {
    val part = new RangePartitioner(numPartitions, self, ascending)
    new ShuffledRDD[K, V, V](self, part)
      .setKeyOrdering(if (ascending) ordering else ordering.reverse)
  }

功能介绍:

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

mapValues()只对v进行操作

需求:

创建一个pairRDD,并将value数据转换成大写。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    mapValuesTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * mapValuesTest():只对Values操作
   * */
  def mapValuesTest(sc: SparkContext): Unit = {
    // 3.1 创建第一个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc")))

    // 3.2 需求实现
    val value: Array[(Int, String)] = rdd.mapValues(_.toUpperCase).collect()

    // 3.3 打印
    println(value.mkString("\n"))
  }
}

mapValues()操作如图所示:

mapValues()函数结构:

  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)
  }

功能介绍:

针对于(K,V)形式的类型只对V进行操作

join()将相同k对应的多个v关联在一起

需求:

创建两个pairRDD,并将key相同的数据聚合到一个元组。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    joinTest(sc)

    //4.关闭连接
    sc.stop()
  }

  /**
   * joinTest():在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
   * */
  def joinTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c"),(0,"零")))
    val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6)))

    // 3.2 需求实现
    val value: Array[(Int, (String, Int))] = rdd.join(rdd1).collect()

    // 3.3 打印
    println(value.mkString("\n"))
  }
}

join()操作如图所示:

join()函数结构:

  def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
    this.cogroup(other, partitioner).flatMapValues( pair =>
      for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w)
    )
  }

功能介绍:

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

扩展:左外连接、右外连接和全外连接

返回类型为:(Int, (String, Option[Int]))。

Option[A] (sealed trait) 有两个取值:
    1. Some[A] 有类型A的值
    2. None 没有值

一般通过f._2._2.getOrElse("默认值")获取值。

左外连接(左连接):左边都有,右边关联不上,用None表示,

def leftOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)

右外连接(右连接):右边都有,左边关联不上,用None表示

def rightOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)

全外连接(全连接):左右都有,关联不上,用None表示,

def fullOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)

cogroup()类似全连接,但是在同一个RDD中对k聚合

需求:

创建两个pairRDD,并将key相同的数据聚合到一个迭代器。

代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    cogroupTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * cogroupTest():在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD
   * */
  def cogroupTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))
    val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (2, 55),(4, 6),(1, 44)))

    // 3.2 需求实现
    val value: Array[(Int, (Iterable[String], Iterable[Int]))] = rdd.cogroup(rdd1).collect()
    
    // 3.3 打印
    println(value.mkString("\n"))
  }
}

cogroup()操作如图所示:

cogroup()函数结构:

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

功能介绍:

在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD

第二部分:Action行动算子

        行动算子是触发了整个作业的执行。因为转换算子都是懒加载,并不会立即执行。常用的行动算子有如下几个。

        说明:为了方便,行动算子的测试也将在object TransformationDemo包下实现。不再新建一个包。

reduce()聚合

1)函数签名:def reduce(f: (T, T) => T): T

2)功能说明:f函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据

3)需求说明:创建一个RDD,将所有元素聚合得到结果

4)代码如下:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    reduceTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * reduce:聚集RDD内所有元素
   * */
  def reduceTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))
    val rdd1: RDD[Int] = sc.makeRDD(1 to 10)

    // 3.2 需求实现
    val value: (Int, String) = rdd.reduce((k1: (Int, String), k2: (Int, String)) => (k1._1 + k2._1,k1._2 + k2._2))
    val value1: Int = rdd1.reduce(_+_)

    // 3.3 打印
    println(value)
    println(value1)
  }
}

collect()以数组的形式返回数据集

1)函数签名:def collect(): Array[T]

2)功能说明:在驱动程序中,以数组Array的形式返回数据集的所有元素。

        注意:所有的数据都会被拉取到Driver端,当数据量大时,会发生OOM,慎用。

3)需求说明:创建一个RDD,并将RDD内容收集到Driver端打印

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    collectTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * collect:收集数据到Driver端,以数组的形式返回。
   * */
  def collectTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))

    // 3.2 需求实现
    val value: Array[(Int, String)] = rdd.collect()

    // 3.3 打印
    value.foreach(println)

  }
}

count()返回RDD中元素个数

1)函数签名:def count(): Long

2)功能说明:返回RDD中元素的个数。

3)需求说明:创建一个RDD,统计该RDD的条数。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    countTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * count:返回RDD的条数
   * */
  def countTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))

    // 3.2 需求实现
    val value: Long = rdd.count()

    // 3.3 打印
    println(value)
  }
}

first()返回RDD中的第一个元素

1)函数签名: def first(): T

2)功能说明:返回RDD中的第一个元素。

3)需求说明:创建一个RDD,返回该RDD中的第一个元素。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    firstTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * first:返回RDD中第一条数据
   * */
  def firstTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (3, "cC"), (2, "b"), (3, "c"),(0,"零"),(1, "aA")))

    // 3.2 需求实现
    val value: (Int, String) = rdd.first()

    // 3.3 打印
    println(value)
  }
}

take(n)返回由RDD前n个元素组成的数组

1)函数签名: def take(num: Int): Array[T]

2)功能说明:返回一个由RDD的前n个元素组成的数组。

3)需求说明:返回RDD中key值最大的前3个元素。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    takeTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * take(n):返回由RDD组成的前n个元素的数组。
   * */
  def takeTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(4, "4c"), (5, "5c"), (3, "cC"), (2, "b"), (6, "c"),(0,"零"),(7, "aA")))

    // 3.2 需求实现
    val value: Array[(Int, String)] = rdd.sortByKey(false).take(3)

    // 3.3 打印
    println(value.mkString("\n"))
  }
}

takeOrdered(n)返回RDD排序后前n个元素组成的数组

1)函数签名: def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]

2)功能说明:返回该RDD排序后的前n个元素组成的数组。(默认升序)

3)需求说明:创建一个RDD,获取该RDD排序后的前2个元素。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[*]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    takeTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * takeOrdered(n):返回该RDD排序后的前n个元素组成的数组.
   * */
  def takeOrderedTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(4, "4c"), (5, "5c"), (3, "cC"), (2, "b"), (6, "c"),(0,"零"),(7, "aA")))

     // 3.2 需求实现
    val value: Array[(Int, String)] = rdd.takeOrdered(5)  // 升序

    // 自定义排序(重写compare方法):降序
    val value1: Array[(Int, String)] = rdd.takeOrdered(5)(new Ordering[(Int,String)](){
      override def compare(x: (Int, String), y: (Int, String)): Int = y._1-x._1
    })

    // 3.3 打印
    println(value.mkString("\n"))
    println(value1.mkString("\n"))
  }
}

aggregate()案例

1)函数签名: def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U

2)功能说明:aggregate函数将每个分区里面的元素通过分区内逻辑和初始值进行聚合然后用分区间逻辑和初始值(zeroValue)进行操作

        注意:1、分区间逻辑再次使用初始值和aggregateByKey是有区别的。

                   2、zeroValue 分区内聚合和分区间聚合的时候各会使用一次。

操作流图:

3)需求说明:创建一个RDD,将所有元素相加得到结果。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[1]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    aggregateTest(sc) 

    //4.关闭连接
    sc.stop()
  }

  /**
   * 行动算子:
   * aggregateTest(n):aggregate函数将每个分区里面的元素通过分区内逻辑和初始值进行聚合,然后用分区间逻辑和初始值(zeroValue)进行操作
   * */
  def aggregateTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(2, "4c"), (3, "5c"), (4, "cC")),2)
    // 3.2 需求实现
    val value: Int = rdd.aggregate(0)(
      (a: Int, b: (Int, String)) => a + b._1,
      (x: Int, y: Int) => x + y
    )

    val value3: Int = rdd.aggregate(10)(
      (a: Int, b: (Int, String)) => a + b._1,
      (x: Int, y: Int) => x + y
    )

    val value2: String = rdd.aggregate("x")(
      (a: String, b: (Int, String)) => a + b._1,
      (x: String, y: String) => x + y
    )

    // 3.3 打印
    println(value)
    println(value2)
    println(value3)
  }

fold()案例

1)函数签名: def fold(zeroValue: T)(op: (T, T) => T): T

2)功能说明:折叠操作,aggregate的简化操作。即,分区内逻辑和分区间逻辑相同。

3)需求说明:创建一个RDD,将所有元素相加得到结果。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[1]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    foldTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * fold:折叠操作,aggregate的简化操作。即,分区内逻辑和分区间逻辑相同。
   * */
  def foldTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"),(2, "4c"), (3, "5c"), (4, "cC")),2)
    // 3.2 需求实现
    val value: (Int, String) = rdd.fold((0," "))((z: (Int, String), x: (Int, String)) => (z._1 + x._1,x._2 + z._2))
    
    // 3.3 打印
    println(value)
  }
}

countByKey()统计每种key的个数

1)函数签名:def countByKey(): Map[K, Long]

2)功能说明:统计每种key的个数。

        注:可以用来查看数据是否倾斜。

3)需求说明:创建一个PairRDD,统计每种key的个数。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[1]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    countByKeyTest(sc)

    //4.关闭连接
    sc.stop()
  }

  /**
   * 行动算子:
   * countByKey:统计每种key的个数
   * */
  def countByKeyTest(sc: SparkContext): Unit = {
    //3.1 创建第两个RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc")))

    // 3.2 需求实现
    val value: collection.Map[Int, Long] = rdd.countByKey()
    // 3.3 打印
    println(value)
  }
}

save相关算子

1saveAsTextFile(path)保存成Text文件

(1)函数签名:def saveAsTextFile(path: String)

(2)功能说明:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本。

2saveAsSequenceFile(path) 保存成Sequencefile文件

(1)函数签名:def saveAsSequenceFile(path: String)

(2)功能说明:将数据集中的元素以Hadoop Sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。

        注意:只有kv类型RDD有该操作,单值的没有

3saveAsObjectFile(path) 序列化成对象保存到文件

(1)函数签名:def saveAsObjectFile(path: String)

(2)功能说明:用于将RDD中的元素序列化成对象,存储到文件中。

代码演示:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[1]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    saveTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * savexxx:将数据持久化到本地(或HDFS)
   * */
  def saveTest(sc: SparkContext): Unit = {
    //3.1 创建RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (2, "b"), (3, "c"), (4, "d"), (1, "aa"), (1, "bb"), (3, "cc")))

    // 3.2 需求实现
    rdd.repartition(1).saveAsTextFile("file:///C:/tmp/output/txt/")
    rdd.repartition(1).saveAsObjectFile("file:///C:/tmp/output/obj/")
    rdd.repartition(1).saveAsSequenceFile("file:///C:/tmp/output/seq/")

  }
}

foreach()&foreachPartition遍历RDD中每一个元素

1)函数签名:def foreach(f: T => Unit): Unit

2)功能说明:遍历RDD中的每一个元素,并依次应用f函数。

两种形式的foreach算子操作如图所示:

 备注:

        1、调用collect算子时,数据在Drive端打印;未调用collect算子时,数据不会返回Drive端,而是直接在Executor端打印。

        2、foreach和foreachPartition的区别:

                foreach 是一条一条的拿数据进行处理;
                foreachPartition是一个分区一个分区的拿数据,一个分区中有很多数据的信息。

        所以,在使用中,当我们要把处理结果保存到数据库中的时候,我们要使用foreachPartition这个方法,这样效率会更高。

3)需求说明:创建一个RDD,对每个元素进行打印。

4)代码实现:

object TransformationDemo {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setMaster("local[1]")
      .setAppName("TransformationDemo_test")

    //2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    //3具体业务逻辑
    foreachTest(sc)

    //4.关闭连接
    sc.stop()
  }
  /**
   * 行动算子:
   * foreach和foreachPartition:遍历RDD中的元素
   * */
  def foreachTest(sc: SparkContext): Unit = {
    //3.1 创建RDD
    val rdd: RDD[Int] = sc.makeRDD(1 to 100,5)

    // 3.2 需求实现
    rdd.foreach((f: Int) => println(TaskContext.getPartitionId() + "--" + f))
    rdd.foreachPartition((f: Iterator[Int]) => println(TaskContext.getPartitionId() + "--" + f.mkString(",")))

  }
}

总结

        至此,常用的RDD算子已全部汇总完毕,当然,还有许多遗漏的算子和方法的使用未涉及到,感兴趣的读者可以自行研究学习。

        下面为读者汇总关于RDD算子中面试常见问题:

  • map与mapPartitions的区别

map():每次处理一条数据。

mapPartitions():每次处理一个分区的数据,这个分区的数据处理完后,原 RDD 中该分区的数据才能释放,可能导致 OOM。

开发指导:当内存空间较大的时候建议使用mapPartitions(),以提高处理效率。

  • coalesce与repartition两个算子的作用以及区别与联系

1)关系:
        两者都是用来改变RDD的partition数量的,repartition底层调用的就是coalesce方法:coalesce(numPartitions, shuffle = true)

2)区别:
        repartition一定会发生shuffle,coalesce根据传入的参数来判断是否发生shuffle。
一般情况下增大rdd的partition数量使用repartition,减少partition数量时使用coalesce。

注:

1、coalesce填写的参数小于等于RDD的partition数量,大于不起作用
2、coalesce中的参数过小可能会导致OOM,这时,又想保证小文件的数量少,可以使用reparation。

  • 使用zip算子时需要注意的是什么(即哪些情况不能使用)

在 Spark 中, 两个 RDD 的元素的数量和分区数都必须相同, 否则会抛出异常。

其实本质就是要求的每个分区的元素的数量相同.

  • aggregateByKey与aggregate之间的区别与联系。

aggregate和aggregateByKey的参数是一样的,作用也一样,只不过aggregateByKey多了key而已。

如果将RDD分别有设置成2个分区和4个分区,则

  • 不影响aggregateByKey()的输出结果,因为其是按照K处理分区内和分区间逻辑。
  • 会影响aggregate的输出结果,2个分区中,初始值共使用了3次;4个分区中,初始值共使用了5次。
  • reduceByKey跟groupByKey之间的区别。

reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。

groupByKey:按照key进行分组,直接进行shuffle。返回结果是RDD[(K, Iterable[V])]。

开发指导:reduceByKey比groupByKey性能更好,并且groupByKey会造成OOM。建议使用。但是需要注意是否会影响业务逻辑。

  • reduceByKey跟aggregateByKey之间的区别与联系。

 reduceByKey可以认为是aggregateByKey的简化版。
 aggregateByKey,分为三个参数(zeroValue、seqOp、combOp),可以自定义初始值、分区内、分区间的操作。

  • combineByKey的参数作用,说明其参数调用时机。

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

(1)createCombiner:V=>C 分组内的创建组合的函数。会遍历分区中的每个key-value对. 如果第一次碰到这个key, 则调用createCombiner函数,传入value, 得到一个C类型的值。(如果不是第一次碰到这个 key, 则不会调用这个方法)

(2)mergeValue:(C,V)=>C 该函数主要是分区内的合并函数,作用在每一个分区内部。如果不是第一个遇到这个key,则调用这个函数进行合并操作。

(3)mergeCombiners:(C,C)=>R 该函数主要是进行多分区合并,此时是将两个C合并为一个C。跨分区合并相同的key的值。

路漫漫其修远兮,吾将上下而求索。                ---屈原 

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值