RDD算子

  • 一.RDD转换算子
  • 1)map
  • 2)mapPartitions
  • 3)mapPartitionsWithIndex
  • 4)flatMap
  • 5)glom
  • 6)groupBy
  • 7)filter
  • 8)sample
  • 9)distinct
  • 10)coalesce
  • 11)repartition
  • 12)sortBy
  • 13)双Value类型{ intersection , union , subtract , zip }
  • 14)Key-Value类型{ partitionBy , reduceByKey , groupByKey , aggregateByKey , foldByKey , combineByKey }
  • 15)join
  • 16)leftOuterJoin和rightOuterJoin
  • 17)cogroup
  • 二. RDD行动算子
  • 1)reduce
  • 2)collect
  • 3)count
  • 4)first
  • 5)take
  • 6)takeOrdered
  • 7) aggregate
  • 8) fold
  • 9) countByKey
  • 10) save相关算子
  • 11) foreach



算子:Operator(操作)


RDD的方法和Scala集合对象的方法不一样


集合对象的方法都是在同一个节点的内存中完成的


RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行


为了区分不同的处理效果,所以将RDD的方法称为算子


RDD的方法外部的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行

一.RDD转换算子

1)map

将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-map
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    //1,2,3,4
    //2,4,6,8

    //转换函数
    //    def mapFunction(num:Int):Int={
    //      num*2
    //    }
    //    val mapRDD: RDD[Int] = rdd.map(mapFunction)

    //    val mapRDD: RDD[Int] = rdd.map((num:Int)=>{num*2})  //匿名函数

    val mapRDD: RDD[Int] = rdd.map(_*2)
    mapRDD.collect().foreach(println)

    sc.stop()
  }
}
2
4
6
8
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-map
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
    //1,2一个分区    3,4一个分区

    //1.rdd的计算一个分区内的数据是一个一个执行逻辑
    //  只有前面一个数据全部的逻辑执行完毕后,才会执行下一个数据
    //  分区内数据的执行是有序的
    //2.不同分区数据计算是无序的


    val mapRDD1= rdd.map(
      x => {
        println("List:"+x)
        x
      }
    )
    val mapRDD2= mapRDD1.map(
      x => {
        println("转换后List:" + x)
        x
      })

    mapRDD2.collect()

    sc.stop()
  }
}
List:1
List:3
转换后List:1
转换后List:3
List:2
List:4
转换后List:2
转换后List:4

2)mapPartitions

将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-mapPartitions
    //mapPartitions:可以以分区为单位进行数据转换操作
    //              但是会将整个分区的数据加载到内存进行引用
    //              如果处理完的数据是不会被释放掉,存在对象的引用
    //              在内存较小,数据量较大的场合下,容易出现内存溢出
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
    val rdd1: RDD[Int] = rdd.mapPartitions(iter => {
      println("一个分区执行一次")
      iter.map(_ * 2)
    })
    rdd1.collect().foreach(println)

    sc.stop()
  }
}
一个分区执行一次
一个分区执行一次
2
4
6
8
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-mapPartitions
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
    //[1,2] [3,4]
    //可以得出各分区内的最大值
    val rdd1 = rdd.mapPartitions(iter => {
      List(iter.max).iterator
    })
    rdd1.collect().foreach(println)

    sc.stop()
  }
}
2
4

map和mapPartitions的区别

  • 数据处理角度
    Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子是以分区为单位进行批处理操作。
  • 功能的角度
    Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变, 所以可以增加或减少数据
  • 性能的角度
    Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处理,所以性能较高。但是mapPartitions 算子会长时间占用内存,那么这样会导致内存可能不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用mapPartitions,使用 map 操作。

3)mapPartitionsWithIndex

将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-mapPartitionsWithIndex
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
    // [1,2] [3,4]
    // [3,4]
    val rdd1: RDD[Int] = rdd.mapPartitionsWithIndex((index, iter) => {
      if (index == 1) {
        iter
      } else {
        Nil.iterator  //Nil返回空集合
      }
    })

    rdd1.collect().foreach(println)
    sc.stop()
  }
}
3
4
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-mapPartitionsWithIndex
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    // 1,2,3,4
    // (分区号,数字)
    val rdd1: RDD[(Int, Int)] = rdd.mapPartitionsWithIndex((index, iter) => {
      iter.map(x => (index, x))
    })

    rdd1.collect().foreach(println)
    sc.stop()
  }
}
(2,1)
(5,2)
(8,3)
(11,4)

4)flatMap

将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-flatMap
    val rdd: RDD[List[Int]] = sc.makeRDD(List(
      List(1, 2), List(3,4)))
    val rdd1: RDD[Int] = rdd.flatMap(x=>x)

    rdd.collect().foreach(println)
    rdd1.collect().foreach(println)
    sc.stop()
  }
}
List(1, 2)
List(3, 4)
1
2
3
4
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-flatMap
    val rdd: RDD[String] = sc.makeRDD(List(
      "hello spark", "hello java"))
    val rdd1: RDD[String] = rdd.flatMap(x => {
      x.split(" ")
    })
    rdd1.collect().foreach(println)

    sc.stop()
  }
}
hello
spark
hello
java
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-flatMap
    //数据类型不一致时,使用模式匹配
    val rdd: RDD[Any] = sc.makeRDD(List(List(1,2),3,List(4,5)))
    val rdd1: RDD[Any] = rdd.flatMap(x => {
      x match {
        case x: List[Int] => x
        case x => List(x)
      }
    })
    rdd1.collect().foreach(println)

    sc.stop()
  }
}
1
2
3
4
5

5)glom

将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-glom
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    //List => Int
    //Int => Array
    val glomRdd: RDD[Array[Int]] = rdd.glom()
    glomRdd.collect().foreach(x=>println(x.mkString(",")))

    sc.stop()
  }
}
1,2
3,4
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-glom
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    //[1,2],[3,4]   分区取最大值
    //[2]   [4]     最大值求和
    //[6]
    val glomRDD: RDD[Array[Int]] = rdd.glom()
    val maxRDD: RDD[Int] = glomRDD.map(
      array => {
        array.max
      }
    )
    println(maxRDD.collect().sum)

    sc.stop()
  }
}
6

6)groupBy

将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为shuffle。极限情况下,数据可能被分在同一个分区中
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-groupBy
    val rdd: RDD[String] = sc.makeRDD(List("hello","spark","hadoop","scala","java"),2)
    val groupRDD: RDD[(Char, Iterable[String])] = rdd.groupBy(_.charAt(0))
    groupRDD.collect().foreach(println)

    sc.stop()
  }
}
(h,CompactBuffer(hello, hadoop))
(j,CompactBuffer(java))
(s,CompactBuffer(spark, scala))

1.txt

uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:02:05:03 +000
uu wr erw 17/05/2015:12:05:03 +000
uu wr erw 17/05/2015:03:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:03:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:11:05:03 +000
uu wr erw 17/05/2015:02:05:03 +000
uu wr erw 17/05/2015:02:05:03 +000
uu wr erw 17/05/2015:12:05:03 +000
uu wr erw 17/05/2015:12:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.textFile("datas\\1.txt")
    val timeRDD: RDD[(String, Iterable[(String, Int)])] = rdd.map(line => {
      val datas: Array[String] = line.split(" ")
      val time: String = datas(3)
      val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss")
      /*
      * parse()返回的是一个Date类型数据
      * parse方法可以把String型的字符串转换成特定格式的date类型,
      * 使用parse时字符串长度要和定义的SimpleDateFormat对象长度一致
      */
      val date: Date = sdf.parse(time)
      println(date)
      println("----------------")
      val sdf1 = new SimpleDateFormat("HH")
      /*
      * format返回的是一个String类型的数据
      * format方法可以把Date型字符转换成特定格式的String类型,
      * 如果Date类型和定义的SimpleDateFormat长度不一致会自动在后面补0
      */
      val hour: String = sdf1.format(date)
      println(hour)
      (hour, 1)

      // 法二:字符串截取
      // val str: String = datas(3).substring(11, 13)
      //(str, 1)
    }).groupBy(_._1)

    timeRDD.map {
      //模式匹配
      case (hour, iter) => (hour, iter.size)
    }.collect().foreach(println)

    sc.stop()
  }
}
Sun May 17 11:05:03 CST 2015
Sun May 17 10:05:03 CST 2015
----------------
----------------
11
10
Sun May 17 02:05:03 CST 2015
----------------
Sun May 17 11:05:03 CST 2015
----------------
02
11
Sun May 17 11:05:03 CST 2015
----------------
11
Sun May 17 02:05:03 CST 2015
----------------
02
Sun May 17 02:05:03 CST 2015
----------------
02
Sun May 17 12:05:03 CST 2015
----------------
12
Sun May 17 12:05:03 CST 2015
----------------
12
Sun May 17 03:05:03 CST 2015
----------------
03
Sun May 17 10:05:03 CST 2015
----------------
10
Sun May 17 10:05:03 CST 2015
----------------
10
Sun May 17 12:05:03 CST 2015
----------------
Sun May 17 10:05:03 CST 2015
----------------
12
10
Sun May 17 03:05:03 CST 2015
----------------
Sun May 17 10:05:03 CST 2015
----------------
03
10
Sun May 17 11:05:03 CST 2015
----------------
11
Sun May 17 10:05:03 CST 2015
----------------
10
(02,3)
(11,4)
(03,2)
(12,3)
(10,6)

7)filter

将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-filter
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    rdd.filter(num=>(num%2!=0)).collect().foreach(println)

    sc.stop()
  }
}
1
3
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-filter
    val rdd: RDD[String] = sc.textFile("datas\\1.txt")
    //过滤10点的数据
    rdd.filter(line=>{
      line.split(" ")(3).startsWith("17/05/2015:10")
    }).collect().foreach(println)

    sc.stop()
  }
}
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000
uu wr erw 17/05/2015:10:05:03 +000

8)sample

根据指定的规则从数据集中抽取数据
(应用场景:数据倾斜)

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-sample
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6,7,8,9,10))

    //sample算子需要传递三个参数
    //1.第一个参数withReplacement表示,抽取数据后是否将数据返回 true(放回),false(丢弃)
    //2.第二个参数fraction表示:如果抽取不放回的场合:数据源中每条数据被抽取的概率,基准值的概念
    //                        如果抽取放回的场合:表示数据源中的每条数据别抽取的可能次数
    //3.第三个参数seed表示,抽取数据时随机算法的种子
    //                    如果不传递第三个参数,那么使用的是当前系统时间
    println(rdd.sample(
      false,
      0.4,
      1
    ).collect().mkString(","))
    println("-------------------")
    println(rdd.sample(
      true,
      2
    ).collect().mkString(","))

    sc.stop()
  }
}
1,2,3,7,9
-------------------
2,2,2,3,3,3,3,4,4,4,5,6,7,7,7,8,8,8,9,9,9,9,10,10,10,10,10,10,10

9)distinct

将数据集中重复的数据去重

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-distinct
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,2,3,2))
    println(rdd.distinct().collect() mkString (","))

    sc.stop()
  }
}
1,2,3,4

10)coalesce

根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-coalesce
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3)
    val newRDD: RDD[Int] = rdd.coalesce(2)
    newRDD.saveAsTextFile("output")

    sc.stop()
  }
}

spark 转字符 spark的转换算子_List

spark 转字符 spark的转换算子_List_02


coalesce方法默认情况下不会将分区的数据打乱重新组合

这种情况下的缩减分区可能会导致数据不均衡,出现数据倾斜

如果想要让数据均衡,可以进行shuffle处理,第二个参数设为true

coalesce算子可以扩大分区的,但是如果不进行shuffle操作,是没有意义的,不起作用如果

想要实现扩大分区的效果,需要使用shuffle操作

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-coalesce
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3)
    val newRDD: RDD[Int] = rdd.coalesce(2,true)
    newRDD.saveAsTextFile("output")

    sc.stop()
  }
}

spark 转字符 spark的转换算子_big data_03

spark 转字符 spark的转换算子_big data_04

11)repartition

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

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-repartition
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),2)
    val newRDD: RDD[Int] = rdd.repartition(3)
    newRDD.saveAsTextFile("output")

    sc.stop()
  }
}

spark 转字符 spark的转换算子_spark_05

spark 转字符 spark的转换算子_big data_06

spark 转字符 spark的转换算子_spark 转字符_07

12)sortBy

该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原RDD 的分区数一致。中间存在 shuffle 的过程

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-sortBy
    //sortBy方法可以根据指定的规则对数据源中的数据进行排序,默认是升序
    //第二个参数可以改变排序的方式,用false
    //sortBy默认情况下,不会改变分区,但是中间存在shuffle操作
    val rdd: RDD[Int] = sc.makeRDD(List(4,3,2,6,4,1))
    println(rdd.sortBy(x => x,false).collect().mkString(","))
    println("---------------")
    val rdd1: RDD[(String, Int)] = sc.makeRDD(List(("1",1),("11",2),("2",3)))
    println(rdd1.sortBy(x => x._1).collect().mkString(","))
    println("---------------")
    val rdd2: RDD[(String, Int)] = sc.makeRDD(List(("1",1),("11",2),("2",3)))
    println(rdd2.sortBy(x => x._1.toInt).collect().mkString(","))

    sc.stop()
  }
}
6,4,4,3,2,1
---------------
(1,1),(11,2),(2,3)
---------------
(1,1),(2,3),(11,2)

13)双Value类型{ intersection , union , subtract , zip }

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子-双Value类型
    //交集,并集和差集要求两个数据源数据类型保持一致
    //拉链操作两个数据源的类型可以不一致
    //拉链操作时,两个数据源要求分区数量要保持一致,分区中数据数量保持一致
    val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))

    //交集
    println(rdd1.intersection(rdd2).collect().mkString(","))
    //并集
    println(rdd1.union(rdd2).collect().mkString(","))
    //差集
    println(rdd1.subtract(rdd2).collect().mkString(","))
    //拉链
    println(rdd1.zip(rdd2).collect().mkString(","))

    sc.stop()
  }
}
3,4
1,2,3,4,3,4,5,6
1,2
(1,3),(2,4),(3,5),(4,6)

14)Key-Value类型{ partitionBy , reduceByKey , groupByKey , aggregateByKey , foldByKey , combineByKey }

partitionBy
将数据按照指定Partitioner 重新进行分区。Spark 默认的分区器是HashPartitioner

object Test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),2)
    val mapRDD: RDD[(Int, Int)] = rdd.map((_,1))
    //partitionBy根据指定的分区规则对数据进行重分区
    mapRDD.partitionBy(new HashPartitioner(2))
      .saveAsTextFile("output")

    sc.stop()
    }
}

spark 转字符 spark的转换算子_scala_08

spark 转字符 spark的转换算子_big data_09


reduceByKey

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- Key-Value类型
    //reduceByKey:相同的key的数据进行value数据的聚合操作
    //reduceByKey分区内和分区间计算规则是相同的
    //Scala语言中一般的聚合操作都是两两聚合,spark基于Scala开发的,所以它的聚合也是两两聚合
    //reduceByKey中如果key的数据只有一个,是不会参与运算的
    val rdd: RDD[(String, Int)] =sc.makeRDD(List(("a",1),("a",2),("a",3),("b",4)))
    val rdd1: RDD[(String, Int)] = rdd.reduceByKey((x,y)=>{
      println(s"x=${x},y=${y}")
      x+y
    })
    rdd1.collect().foreach(println)

    sc.stop()
  }
}
x=1,y=2
x=3,y=3
(a,6)
(b,4)

groupByKey

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- Key-Value类型
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("a",3),("b",4)))

    //groupByKey: 将数据源中的数据,相同key的数据分在一个组中,形成一个对偶元组
    //             元组中的第一个元素就是key
    //             元组中的第二个元素就是相同key的value的
    val rdd1: RDD[(String, Iterable[Int])] = rdd.groupByKey()
    rdd1.collect().foreach(println)
    println("----------------------")
    val rdd2: RDD[(String, Iterable[(String, Int)])] = rdd.groupBy(_._1)
    rdd2.collect().foreach(println)

    sc.stop()
  }
}
(a,CompactBuffer(1, 2, 3))
(b,CompactBuffer(4))
----------------------
(a,CompactBuffer((a,1), (a,2), (a,3)))
(b,CompactBuffer((b,4)))

groupByKey和reduceByKey的区别

  • 从 shuffle 的角度:
    reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是reduceByKey 可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的数据量,而groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高。
  • 从功能的角度:
    reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那么还是只能使用groupByKey

aggregateByKey
将数据根据不同的规则进行分区内计算和分区间计算

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- Key-Value类型
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",6),("b",4),("a",6),("b",2)),2)
    //取出每个分区内相同 key 的最大值然后分区间相加

    //aggregateByKey存在函数柯里化,有两个参数列表
    //第一个参数列表,需要传递一个参数,表示为初始值
    //             主要用于当碰见第一个key的时候,和value进行分区内计算
    //第二个参数列表需要传递两个参数:
    //                            第一个参数表示分区内计算规则
    //                            第二个参数表示分区间计算规则
    val rdd1: RDD[(String, Int)] = rdd.aggregateByKey(5)(
      (x, y) => math.max(x, y),
      (x, y) => x + y
    )
    rdd1.collect().foreach(println)

    sc.stop()
  }
}
(b,11)
 (a,11)

foldByKey
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为foldByKey

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- Key-Value类型
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",6),("b",2)),2)
    val rdd1: RDD[(String, Int)] = rdd.foldByKey(0)(_+_)
    rdd1.collect().foreach(println)

    sc.stop()
  }
}
(b,6)
(a,9)

combineByKey

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- Key-Value类型
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",6),("b",2)),2)
    //combineByKey:方法需要三个参数
    //第一个参数表示:将相同key的第一个数据进行结构的转换,实现操作
    //第二个参数表示:分区内的计算规则
    //第三个参数表示:分区间的计算规则
    val rdd1: RDD[(String, (Int, Int))] = rdd.combineByKey(
      v => (v, 1),
      (t: (Int, Int), v) => {
        (t._1 + v, t._2 + 1)
      },
      (t1: (Int, Int), t2: (Int, Int)) => {
        (t1._1 + t2._1, t1._2 + t2._2)
      }
    )
    rdd1.collect().foreach(println)
    println("---------------------")
    val rdd2: RDD[(String, Int)] = rdd1.mapValues {
      case (num, cnt) => {
        num / cnt
      }
    }
    rdd2.collect().foreach(println)

    sc.stop()
  }
}
(b,(6,2))
(a,(9,3)
---------------------
(b,3)
(a,3)

reduceByKey、foldByKey、aggregateByKey、combineByKey的区别

  • reduceByKey: 相同 key 的第一个数据不进行任何计算,分区内和分区间计算规则相同
  • FoldByKey: 相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则相同
  • AggregateByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则可以不相同
  • CombineByKey:当计算时,发现数据结构不满足要求时,可以让第一个数据转换结构。分区内和分区间计算规则不相同。
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- Key-Value类型
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",4),("a",6),("b",2)),2)

    //wordcount
    println(rdd.reduceByKey(_ + _).collect().mkString(","))
    println(rdd.aggregateByKey(0)(_+_,_+_).collect().mkString(","))
    println(rdd.foldByKey(0)(_+_).collect().mkString(","))
    println(rdd.combineByKey(v=>v,(x:Int,y)=>(x+y),(x:Int,y:Int)=>(x+y)).collect().mkString(","))

    sc.stop()
  }
}

15)join

join会导致数据量几何增长,并且会影响shuffle的性能,不推荐使用

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- join
    //join:两个不同数据源的数据,相同的key的value会连接在一起,形成元组
    //如果两个数据源中key没有匹配上,那么数据不会出现在结果中
    //如果两个数据源中key有多个相同的,会依次匹配,可能会出现笛卡尔乘积,数据量会几何性增长,会导致性能降低
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("b",4),("c",2)))
    val rdd1: RDD[(String, Any)] = sc.makeRDD(List(("a","g"),("b",5),("d",6),("a",2)))
    val rdd2: RDD[(String, (Int, Any))] = rdd.join(rdd1)
    rdd2.collect().foreach(println)

    sc.stop()
  }
}
(a,(1,g))
(a,(1,2))
(b,(4,5))

16)leftOuterJoin和rightOuterJoin

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- 左右连接
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("b",4),("c",2)))
    val rdd1: RDD[(String, Any)] = sc.makeRDD(List(("a","g"),("b",5)))
    val rdd2: RDD[(String, (Int, Option[Any]))] = rdd.leftOuterJoin(rdd1)
    val rdd3: RDD[(String, (Option[Int], Any))] = rdd.rightOuterJoin(rdd1)

    rdd2.collect().foreach(println)
    println("---------------------")
    rdd3.collect().foreach(println)

    sc.stop()
  }
}
(a,(1,Some(g)))
(b,(4,Some(5)))
(c,(2,None))
---------------------
(a,(Some(1),g))
(b,(Some(4),5))

17)cogroup

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //TODO 算子- cogroup  
    //cogroup-connection+group
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("b",4),("c",2)))
    val rdd1: RDD[(String, Any)] = sc.makeRDD(List(("a","g"),("b",5),("b","m")))
    val rdd2: RDD[(String, (Iterable[Int], Iterable[Any]))] = rdd.cogroup(rdd1)
    rdd2.collect().foreach(println)

    sc.stop()
  }
}
(a,(CompactBuffer(1),CompactBuffer(g)))
(b,(CompactBuffer(4),CompactBuffer(5, m)))
(c,(CompactBuffer(2),CompactBuffer()))

二. RDD行动算子

  • 所谓的行动算子,其实就是触发作业(job)执行的方法,通俗点就是触发整个计算的执行
  • 底层代码调用的是环境对象的runJob方法
  • 底层代码中会创建ActiveJob,并提交执行

1)reduce

聚集RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    //TODO-行动算子-reduce
    val i: Int = rdd.reduce(_+_)
    println(i)

    sc.stop()

  }
}
10

2)collect

在驱动程序中,以数组Array 的形式返回数据集的所有元素

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    //TODO-行动算子-collect
    //collect:方法会将不同分区数的数据按照分区顺序采集到Driver端内存中,形成数组
    val ints: Array[Int] = rdd.collect()
    println(ints.mkString(","))
    
    sc.stop()

  }
}
1,2,3,4

3)count

返回RDD 中元素的个数

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    //TODO-行动算子-count
    println(rdd.count())
    
    sc.stop()
  }
}
4

4)first

返回RDD 中的第一个元素

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    //TODO-行动算子-first
    println(rdd.first())
    
    sc.stop()
  }
}
1

5)take

返回一个由RDD 的前 n 个元素组成的数组

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    //TODO-行动算子-take
    println(rdd.take(3).mkString(","))

    sc.stop()
  }
}
1,2,3

6)takeOrdered

返回该 RDD 排序后的前 n 个元素组成的数组

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(4,2,3,1))

    //TODO-行动算子-takeOrdered
    println(rdd.takeOrdered(2).mkString(","))
    
    sc.stop()
  }
}
1,2

7) aggregate

分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    //TODO-行动算子-aggregate
    //aggregateByKey:初始值只会参加分区内计算
    //aggregate:初始值只会参与分区内计算,并且会参与分区间计算
    val i: Int = rdd.aggregate(2)(_+_,_*_)
    println(i)
    
    sc.stop()
  }
}
90

8) fold

折叠操作,aggregate 的简化版操作,分区内和分区间操作一致时

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    //TODO-行动算子-fold
    val i: Int = rdd.fold(2)(_+_)
    println(i)

    sc.stop()
  }
}
16

9) countByKey

统计每种 key 的个数

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd1: RDD[Int] = sc.makeRDD(List(1,1,1,4))
    val rdd2: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",1)))

    //TODO-行动算子-countByKey
    val intToLong: collection.Map[Int, Long] = rdd1.countByValue()
    val stringToLong: collection.Map[String, Long] = rdd2.countByKey()

    println(intToLong)
    println(stringToLong)
    
    sc.stop()
  }
}
Map(1 -> 3, 4 -> 1)
Map(a -> 2, b -> 1)

10) save相关算子

将数据保存到不同格式的文件中

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",2),("b",3)),1)

    //TODO -行动算子 save相关算子
    rdd.saveAsTextFile("output")
    rdd.saveAsObjectFile("output1")
    //saveAsSequenceFile方法要求数据的格式必须为K-V类型
    rdd.saveAsSequenceFile("output2")
    
    sc.stop()
  }
}

output

spark 转字符 spark的转换算子_big data_10


output1

spark 转字符 spark的转换算子_List_11


output2

spark 转字符 spark的转换算子_spark_12

11) foreach

分布式遍历RDD 中的每一个元素,调用指定函数

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    //foreach其实是Driver端内存集合的循环遍历方法
    //先采集再循环
    rdd.collect().foreach(println)
    println("---------")
    //foreach其实是Executor端内存数据打印
    //不是按顺序采集数据
    rdd.foreach(println)

    sc.stop()
  }
}
1
2
3
4
---------
2
3
4
1
object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    val user = new User()
    rdd.foreach(
      num=>{
        println("age="+(user.age+num))
      }
    )

    sc.stop()
  }
  //  class User extends Serializable {
  //样例类在编译时,会自动混入序列化特质(实现可序列化接口)
  case class User(){
    var age:Int=30
  }
}
age=32
age=33
age=34
age=31

闭包检测

object Spark_rdd_01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RDD").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List[Int]())

    //RDD算子中传递的函数是会包含闭包(算子内使用到外部变量)操作
    // 那么就会进行检测功能,检测是否序列化,称为闭包检测
    val user = new User()
   //因为rdd没有值,所以传递的外部变量所在类没有进行序列化,看是否报错
    rdd.foreach(
      num=>{
        println("age="+(user.age+num))
      }
    )

    sc.stop()
  }
 //未进行序列化,看是否报错
  class User  {
    var age:Int=30
  }
}
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
 at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:403)
 at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:393)
 at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:162)
 at org.apache.spark.SparkContext.clean(SparkContext.scala:2326)
 at org.apache.spark.rdd.RDD$$anonfun$foreach$1.apply(RDD.scala:926)
 at org.apache.spark.rdd.RDD$$anonfun$foreach$1.apply(RDD.scala:925)
 at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
 at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
 at org.apache.spark.rdd.RDD.withScope(RDD.scala:363)
 at org.apache.spark.rdd.RDD.foreach(RDD.scala:925)
 at spark.test.spark.core.rdd.transform.Spark_rdd_01$.main(Spark_rdd_01.scala:18)
 at spark.test.spark.core.rdd.transform.Spark_rdd_01.main(Spark_rdd_01.scala)
Caused by: java.io.NotSerializableException: spark.test.spark.core.rdd.transform.Spark_rdd_01$User
Serialization stack:
 - object not serializable (class: spark.test.spark.core.rdd.transform.Spark_rdd_01$User, value: spark.test.spark.core.rdd.transform.Spark_rdd_01$User@4f824872)
 - field (class: spark.test.spark.core.rdd.transform.Spark_rdd_01$$anonfun$main$1, name: user$1, type: class spark.test.spark.core.rdd.transform.Spark_rdd_01$User)
 - object (class spark.test.spark.core.rdd.transform.Spark_rdd_01$$anonfun$main$1, <function1>)
 at org.apache.spark.serializer.SerializationDebugger$.improveException(SerializationDebugger.scala:40)
 at org.apache.spark.serializer.JavaSerializationStream.writeObject(JavaSerializer.scala:46)
 at org.apache.spark.serializer.JavaSerializerInstance.serialize(JavaSerializer.scala:100)
 at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:400)
 ... 11 more