Spark RDD常用算子使用总结


概述

对于 Spark 处理的大量数据而言,会将数据切分后放入RDD作为Spark 的基本数据结构,开发者可以在 RDD 上进行丰富的操作,之后 Spark 会根据操作调度集群资源进行计算。总结起来,RDD 的操作主要可以分为 Transformation 和 Action 两种。
在这里插入图片描述
官方文档

  • (1)Transformation 转换操作:返回一个新的RDD
    • which create a new dataset from an existing one
    • 所有Transformation函数都是Lazy,不会立即执行,需要Action函数触发
  • (2)Action动作操作:返回值不是RDD(无返回值或返回其他的)
    • which return a value to the driver program after running a computation on the datase
    • 所有Action函数立即执行(Eager),比如count、first、collect、take等
      在这里插入图片描述

此外注意RDD中函数细节:

  • 第一点:RDD不实际存储真正要计算的数据,而是记录了数据的位置在哪里,数据的转换关系(调用了什么方法,传入什么函数);
  • 第二点:RDD中的所有转换都是惰性求值/延迟执行的,也就是说并不会直接计算。只有当发生一个要求返回结果给Driver的Action动作时,这些转换才会真正运行。之所以使用惰性求值/延迟执行,是因为这样可以在Action时对RDD操作形成DAG有向无环图进行Stage的划分和并行优化,这种设计让Spark更加有效率地运行

Transformation(转换算子)

在Spark中Transformation操作表示将一个RDD通过一系列操作变为另一个RDD的过程,这个操作可能是简单的加减操作,也可能是某个函数或某一系列函数。值得注意的是Transformation操作并不会触发真正的计算,只会建立RDD间的关系图

如下图所示,RDD内部每个方框是一个分区。假设需要采样50%的数据,通过sample函数,从 V1、V2、U1、U2、U3、U4 采样出数据 V1、U1 和 U4,形成新的RDD。
在这里插入图片描述


1. map

源码:

def map[U](f : scala.Function1[T, U])(implicit evidence$3 : scala.reflect.ClassTag[U]) : org.apache.spark.rdd.RDD[U] = { /* compiled code */ }
  • 表示将 RDD 经由某一函数 f 后,转变为另一个RDD。

只需要传入一个函数即可,如下代码,将原来的Seq集合中每个元素都乘以10,再返回结果,如下:

  @Test
  def mapTest(): Unit = {
    // 1. 创建RDD
    val rdd1 = sc.parallelize(Seq(1, 2, 3))
    // 2. 执行 map 操作
    val rdd2 = rdd1.map(item => item * 10)
    // 3. 得到结果
    val result = rdd2.collect() //通过调用collect来返回一个数组,然后打印输出
    result.foreach(item => println(item))
  }

运行结果:

10
20
30

Process finished with exit code 0

2. flatMap

源码:

def flatMap[U](f : scala.Function1[T, scala.TraversableOnce[U]])(implicit evidence$4 : scala.reflect.ClassTag[U]) : org.apache.spark.rdd.RDD[U] = { /* compiled code */ }
  • 表示将 RDD 经由某一函数 f 后,转变为一个新的 RDD,但是与 map 不同,RDD 中的每一个元素会被映射成新的 0 到多个元素(f 函数返回的是一个序列 Seq)。

代码演示:

  @Test
  def flatMapTest() = {
    // 1. 创建RDD
    val rdd1 = sc.parallelize(Seq("Hello 吕布", "Hello 貂蝉", "Hello 铠"))
    // 2. 处理数据
    val rdd2 = rdd1.flatMap(item => item.split(" "))
    // 3. 查看结果
    val result = rdd2.collect()
    result.foreach(item => println(item))
    // 4. 关闭资源
    sc.stop()
  }

运行结果:

Hello
吕布
Hello
貂蝉
Hello
铠

Process finished with exit code 0

3. filter

源码:

def filter(f : scala.Function1[T, scala.Boolean]) : org.apache.spark.rdd.RDD[T] = { /* compiled code */ }
  • filter 可以过滤掉数据集中的一部分元素
  • filter 中接受的函数,参数是每一个元素,如果这个函数返回true,当前元素就会被加入新数据集,如果返回false,当前元素会被过滤掉

代码演示:

  @Test
  def filter() = {
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
      .filter(item => item % 2 == 0)  //取偶数
      .collect()
      .foreach(item => println(item))
  }

运行结果:

2
4
6
8
10

Process finished with exit code 0

4. mapPartitions

源码:

def mapPartitions[U](f : scala.Function1[scala.Iterator[T], scala.Iterator[U]], preservesPartitioning : scala.Boolean = { /* compiled code */ })(implicit evidence$6 : scala.reflect.ClassTag[U]) : org.apache.spark.rdd.RDD[U] = { /* compiled code */ }
  • mapPartitions 和 map算子一样,只不过map是针对每一条数据进行转换,mapPartitions针对一整个分区的数据进行转换
  • map的func参数是单条数据,mapPartitions的func参数是一个集合(一个分区所有的数据)
  • map的func返回值也是单条数据,mapPartitions的func返回值是一个集合

代码演示:

  @Test
  def mapPartitions(): Unit = {
    // 1. 数据生成
    // 2. 算子使用
    // 3. 获取结果
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6), numSlices = 2)
      .mapPartitions(iter => {
        iter.foreach(item => println(item))
        iter
      })
      .collect()
  }

运行结果:

1
4
2
5
3
6

Process finished with exit code 0

如果想给上述集合中的元素都乘以10该,如何操作?

  @Test
  def mapPartitions2(): Unit = {
    // 1. 数据生成
    // 2. 算子使用
    // 3. 获取结果
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6), numSlices = 2)
      .mapPartitions(iter => {
        // 如果想给集合中的数字都乘10,该如何操作?
        // 遍历 iter 其中每一条数据进行转换,转换完成之后,返回 iter
        val result = iter.map(item => item * 10)  //注意这个的map算子并不是RDD中的,而是Scala中的
        result
      })
      .collect()
      .foreach(item => println(item))
  }

运行结果:

10
20
30
40
50
60

Process finished with exit code 0

5. mapPartitionsWithIndex

源码:

def mapPartitionsWithIndex[U](f : scala.Function2[scala.Int, scala.Iterator[T], scala.Iterator[U]], preservesPartitioning : scala.Boolean = { /* compiled code */ })(implicit evidence$9 : scala.reflect.ClassTag[U]) : org.apache.spark.rdd.RDD[U] = { /* compiled code */ }
  • mapPartitionsWithIndex 和 mapPartitions 的区别是 func 参数中多了一个参数,分区号
  @Test
  def mapPartitionsWithIndex(): Unit = {
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6), numSlices = 2)
      .mapPartitionsWithIndex( (index, iter) => {
        println("index: " + index)
        iter.foreach(item => println(item))
        iter
      } )
      .collect()
  }

运行结果:

index: 0
1
2
3
index: 1
4
5
6

Process finished with exit code 0

运行结果也有可能是这样:原因是RDD的并发性质

index: 1
index: 0
4
5
6
1
2
3

Process finished with exit code 0

6. sample

源码:

def sample(withReplacement : scala.Boolean, fraction : scala.Double, seed : scala.Long = { /* compiled code */ }) : org.apache.spark.rdd.RDD[T] = { /* compiled code */ }
  • 采样,尽可能减少数据集的规律损失
  • withReplacement 参数决定有放回或者无放回采样
  • fraction 参数是采样比例
  @Test
  def sample() = {
    val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
    val rdd2 = rdd1.sample(false, 0.6)
    //第一个参数为false代表无放回采样,0.6是采样比例
    val result = rdd2.collect()
    result.foreach(item => println(item))
  }

运行结果:

3
4
5
6
7
8
9

Process finished with exit code 0

7. mapValues

源码:

def mapValues[U](f : scala.Function1[V, U]) : org.apache.spark.rdd.RDD[scala.Tuple2[K, U]] = { /* compiled code */ }
  • mapValues 也是 map,只不过map作用于整条数据,mapValues作用于 Value
  @Test
  def mapValues() = {
    sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3)))
      .mapValues(item => item * 10)
      .collect()
      .foreach(println(_))
  }

运行结果:

(a,10)
(b,20)
(c,30)

Process finished with exit code 0

8. union(并集)

  @Test
  def union() = {
    val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5))
    val rdd2 = sc.parallelize(Seq(3, 4, 5, 6, 7))

    rdd1.union(rdd2)
      .collect()
      .foreach(println(_))
  }

运行结果:

1
2
3
4
5
3
4
5
6
7

Process finished with exit code 0

9. substract(差集)

  @Test
  def subtract() = {
    val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5))
    val rdd2 = sc.parallelize(Seq(3, 4, 5, 6, 7))

    rdd1.subtract(rdd2) //rdd1-rdd2
      .collect()
      .foreach(println(_))
  }

运行结果:

1
2

Process finished with exit code 0

10. reduceByKey

源码:

def reduceByKey(func : scala.Function2[V, V, V]) : org.apache.spark.rdd.RDD[scala.Tuple2[K, V]] = { /* compiled code */ }
  • 聚合操作时,往往聚合过程中需要中间临时变量(到底时几个变量,具体业务而定)
  @Test
  def reduceByKey() = {
    // 1.创建RDD
    val rdd1 = sc.parallelize(Seq("Hello 吕布", "Hello 貂蝉", "Hello 铠"))
    // 2.处理数据
    val rdd2 = rdd1.flatMap(item => item.split(" "))
      .map(item => (item, 1))
      .reduceByKey((curr, agg) => curr + agg) //注意agg是一个临时变量,或者局部结果,起始值为0
    // 3.得到结果
    val result = rdd2.collect()
    result.foreach(item => println(item))
    // 4.关闭资源
    sc.stop()

运行结果:

(,1)
(貂蝉,1)
(Hello,3)
(吕布,1)

Process finished with exit code 0

11. groupByKey

RDD中groupByKey和reduceByKey区别???

  • reduceByKey函数:在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。简而言之,分组聚合
    在这里插入图片描述
  • groupByKey函数:在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的函数,将相同key的值聚合到一起,与reduceByKey的区别是只生成一个sequence。简而言之就是只分组,不聚合
    在这里插入图片描述
  @Test
  def groupByKey() = {
    sc.parallelize(Seq(("a", 1), ("a", 1), ("c", 3)))
      .groupByKey()
      .collect()
      .foreach(println(_))  //只有一个参数打印输出可以简写
  }

运行结果:

(a,CompactBuffer(1, 1))
(c,CompactBuffer(3))

Process finished with exit code 0

12. combineByKey

源码:

def combineByKey[C](
	createCombiner : scala.Function1[V, C], 
	mergeValue : scala.Function2[C, V, C], 
	mergeCombiners : scala.Function2[C, C, C]
) : org.apache.spark.rdd.RDD[scala.Tuple2[K, C]] = { /* compiled code */ }
  • CombineByKey 算子中接受三个参数:
    • 转换数据的函数(初始函数,作用于第一条数据,用于开启整个计算),在分区上进行聚合,把所有分区的聚合结果聚合为最终结果
  @Test
  def combineByKey() = {
    // 1.准备集合
    val rdd: RDD[(String, Double)] = sc.parallelize(Seq(
      ("铠", 100.0),
      ("耀", 99.0),
      ("镜", 99.0),
      ("镜", 98.0),
      ("铠", 97.0)
    ))
    // 2.算子操作
    //  2.1 createCombiner 转换数据
    //  2.2 mergeValue 分区上的聚合
    //  2.3 mergeCombiners 把分区上的结果再次聚合,生成最终结果
    val combineResult = rdd.combineByKey(
      createCombiner = (curr: Double) => (curr, 1),
      mergeValue = (curr: (Double, Int), nextValue: Double) => (curr._1 + nextValue, curr._2 + 1),
      mergeCombiners = (curr: (Double, Int), agg:(Double, Int)) => (curr._1 + agg._1, curr._2 + agg._2)
    )

    val resultRDD = combineResult.map( item => (item._1, item._2._1 / item._2._2))

    // 3. 输出数据
    resultRDD.collect().foreach(println(_))
  }

运行结果:

(,98.5)
(耀,99.0)
(,98.5)

Process finished with exit code 0

13. foldByKey

源码:

def foldByKey(zeroValue : V)(func : scala.Function2[V, V, V]) : org.apache.spark.rdd.RDD[scala.Tuple2[K, V]] = { /* compiled code */ }
  • foldByKey 和 reduceByKey 的区别是可以指定初始值
  • foldByKey 和 Scala中的 foldLeft、foldRight 区别是,这个初始值作用于每一个数据
  @Test
  def foldByKey() = {
    sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
      .foldByKey(10)((curr, agg) => curr + agg)
      .collect()
      .foreach(println(_))
  }

运行结果:

(a,22)
(b,11)

Process finished with exit code 0

14. aggregateByKey

源码:

def aggregateByKey[U](zeroValue : U)(seqOp : scala.Function2[U, V, U], combOp : scala.Function2[U, U, U])(implicit evidence$3 : scala.reflect.ClassTag[U]) : org.apache.spark.rdd.RDD[scala.Tuple2[K, U]] = { /* compiled code */ }
  • aggregateByKey(zeroValue)(seqOp, combOp)
    • zeroValue:指定初始值
    • seqOp:作用于每个元素,根据初始值,进行计算
    • combOp:将 seqOp 处理过的结果进行聚合
  • aggregateByKey 比较适合针对每个数据要先处理,后聚合的场景
  @Test
  def aggregateByKey() = {
    val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
    rdd.aggregateByKey(0.8)((zeroValue, item) => item * zeroValue, (curr, agg) => curr + agg)
      .collect()
      .foreach(println(_))
  }

运行结果:

(手机,20.0)
(电脑,16.0)

Process finished with exit code 0

15. join

源码:

def join[W](
	other : org.apache.spark.rdd.RDD[scala.Tuple2[K, W]]
	) : org.apache.spark.rdd.RDD[scala.Tuple2[K, scala.Tuple2[V, W]]] = { /* compiled code */ }

在这里插入图片描述

  @Test
  def join() = {
    val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("b", 1)))
    val rdd2 = sc.parallelize(Seq(("a", 10), ("a", 11), ("b", 12)))
    rdd1.join(rdd2)
      .collect()
      .foreach(println(_))
  }

运行结果:

(a,(1,10))
(a,(1,11))
(a,(2,10))
(a,(2,11))
(b,(1,12))

Process finished with exit code 0

16. sortBy

源码:

def sortBy[K](f : scala.Function1[T, K], ascending : scala.Boolean = { /* compiled code */ }, numPartitions : scala.Int = { /* compiled code */ })(implicit ord : scala.Ordering[K], ctag : scala.reflect.ClassTag[K]) : org.apache.spark.rdd.RDD[T] = { /* compiled code */ }
  • sortBy 可以用于任何类型数据的RDD,sortByKey 只有 KV 类型数据的RDD中才有
  • sortBy 可以按照任何部分顺序来排序,sortByKey 只能按照 Key 来排序
  • sortByKey 写发简单,不用编写函数了
  @Test
  def sort() = {
    val rdd1 = sc.parallelize(Seq(2, 4, 1, 5, 1, 8))
    val rdd2 = sc.parallelize(Seq(("a", 1), ("b", 3), ("c", 2)))

    println("-----------------------------")
    rdd1.sortBy(item => item).collect().foreach(println(_))
    println("-----------------------------")
    rdd2.sortBy(item => item._2).collect().foreach(println(_))
    println("-----------------------------")
    rdd2.sortByKey().collect().foreach(println(_))
  }

运行结果:

-----------------------------
1
1
2
4
5
8
-----------------------------
(a,1)
(c,2)
(b,3)
-----------------------------
(a,1)
(b,3)
(c,2)

Process finished with exit code 0

17. repartition

源码:

def repartition(numPartitions : scala.Int)(implicit ord : scala.Ordering[T] = { /* compiled code */ }) : org.apache.spark.rdd.RDD[T] = { /* compiled code */ }
  • repartition 进行重分区的时候,默认是 shuffle 的
  • coalesce 进行重分区的时候,默认是不 shuffle 的,coalesce 默认不能增大分区数
  @Test
  def partitioning() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4, 5), 2)

    println(rdd.repartition(5).partitions.size)
    println(rdd.repartition(1).partitions.size)
    println(rdd.coalesce(5, shuffle = true ).partitions.size)
  }

}
5
1
5

Process finished with exit code 0


Action(执行算子)

不同于Transformation操作,Action操作代表一次计算的结束,不再产生新的 RDD,将结果返回到Driver程序或者输出到外部。所以Transformation操作只是建立计算关系,而Action 操作才是实际的执行者。每个Action操作都会调用SparkContext的runJob 方法向集群正式提交请求,所以每个Action操作对应一个Job。


1. reduce

源码:

def reduce(f : scala.Function2[T, T, T]) : T = { /* compiled code */ }
  • 函数中传入的 curr参数,并不是 Value,而是一整条数据
  • reduce 整体上的结果,只有一个
  • 聚合的时候,往往需要聚合 中间临时变量
  @Test
  def reduce() = {
    val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
    val result: (String, Double) = rdd.reduce((curr, agg) => ("总价", curr._2 + agg._2))
    println(result) // reduce的结果是一个元组
  }

运行结果:

(总价,45.0)

Process finished with exit code 0

2. foreach

源码:

def foreach(f : scala.Function1[T, scala.Unit]) : scala.Unit = { /* compiled code */ }
  • RDD中自带的foreach算子,注意输出的结果顺序不一定按照原来Seq集合中的顺序,是因为RDD是并行计算,异步操作。
  @Test
  def foreach() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4))
    rdd.foreach(item => println(item))
  }

运行结果:

3
1
2
4

Process finished with exit code 0

3. count、countByKey、countByValue

  • count 和 countByKey 的结果相距很远,每次调用 Action 都会生成一个 job,job 会运行获取结果,所以在俩个 job中间有大量的 Log,其实就是在启动job
  • countByKey的运算结果是一个Map型数据:Map(a -> 2, b -> 1, c -> 1)
  • 数据倾斜:如果要解决数据倾斜,是不是要先知道谁倾斜,通过countByKey可以查看Key对应的数量,从而解决倾斜问题
  @Test
  def count() = {
    val rdd = sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3), ("a", 4)))
    println(rdd.count())  // 求出集合中数据的总数
    println(rdd.countByKey()) // 得出 Key
    println(rdd.countByValue())
  }

运行结果:

4
Map(a -> 2, b -> 1, c -> 1)
Map((b,2) -> 1, (c,3) -> 1, (a,1) -> 1, (a,4) -> 1)

Process finished with exit code 0

4. take、takeSample、first

  • take() 和 takeSample() 都是获取数据,一个是直接获取,一个是采样获取(又放回、无放回)
  • first:一般情况下,action 会从所有分区获取数据,相对来说速度比较慢,first 只是获取第一个元素所有只会处理第一个分区,所以速度很快,无需处理所有数据
  @Test
  def take() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4, 5, 6))
    rdd.take(3).foreach(item => println(item))  // 返回前N个数据
    println(rdd.first())  // 返回第一个元素
    rdd.takeSample(withReplacement = false, num = 3).foreach(item => println(item))
  }

运行结果:

1
2
3
1
2
1
5

Process finished with exit code 0

5. max、min、mean、sum(数字运算)

  • 没有中位数,缺陷!
  @Test
  def numberic() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4, 5, 10, 99, 120, 7))
    println(rdd.max())  // 最大值
    println(rdd.min())  // 最小值
    println(rdd.mean()) // 均值
    println(rdd.sum())  //求和
  }

运行结果:

120
1
27.888888888888893
251.0

Process finished with exit code 0


代码汇总

transformation 部分代码汇总

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.junit.Test

class TransformationOp {

  val conf: SparkConf = new SparkConf().setAppName("transformation_op").setMaster("local[6]")
  val sc = new SparkContext(conf)

  /*
    mapPartitions 和 map算子一样,只不过map是针对每一条数据进行转换,mapPartitions针对一整个
    分区的数据进行转换,所以:
      * 1. map的func参数是单条数据,mapPartitions的func参数是一个集合(一个分区所有的数据)
      * 2. map的func返回值也是单条数据,mapPartitions的func返回值是一个集合
   */


  @Test
  def mapPartitions(): Unit = {
    // 1. 数据生成
    // 2. 算子使用
    // 3. 获取结果
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6), numSlices = 2)
      .mapPartitions(iter => {
        iter.foreach(item => println(item))
        iter
      })
      .collect()
  }


  @Test
  def mapPartitions2(): Unit = {
    // 1. 数据生成
    // 2. 算子使用
    // 3. 获取结果
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6), numSlices = 2)
      .mapPartitions(iter => {
        // 如果想给集合中的数字都乘10,该如何操作?
        // 遍历 iter 其中每一条数据进行转换,转换完成之后,返回 iter
        val result = iter.map(item => item * 10)  //注意这个的map算子并不是RDD中的,而是Scala中的
        result
      })
      .collect()
      .foreach(item => println(item))
  }


  /*
    mapPartitionsWithIndex 和 mapPartitions 的区别是 func 参数中多了一个参数,分区号
   */
  @Test
  def mapPartitionsWithIndex(): Unit = {
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6), numSlices = 2)
      .mapPartitionsWithIndex( (index, iter) => {
        println("index: " + index)
        iter.foreach(item => println(item))
        iter
      } )
      .collect()
  }

  /*
    filter 可以过滤掉数据集中的一部分元素
    filter 中接受的函数,参数是每一个元素,如果这个函数返回true,当前元素就会被加入新数据集,
          如果返回false,当前元素会被过滤掉
   */
  @Test
  def filter() = {
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
      .filter(item => item % 2 == 0)  //取偶数
      .collect()
      .foreach(item => println(item))
  }

  /*
    sample 作用:采样,尽可能减少数据集的规律损失
   */
  @Test
  def sample() = {
    val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
    val rdd2 = rdd1.sample(false, 0.6)
    //第一个参数为false代表无放回采样,0.6是采样比例
    val result = rdd2.collect()
    result.foreach(item => println(item))
  }

  /*
    mapValues 也是 map,只不过map作用于整条数据,mapValues作用于 Value
   */
  @Test
  def mapValues() = {
    sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3)))
      .mapValues(item => item * 10)
      .collect()
      .foreach(println(_))
  }

  /*
    交集
   */
  @Test
  def intersection() = {
    val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5))
    val rdd2 = sc.parallelize(Seq(3, 4, 5, 6, 7))

    rdd1.intersection(rdd2)
      .collect()
      .foreach(println(_))
  }

  /*
    并集
   */
  @Test
  def union() = {
    val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5))
    val rdd2 = sc.parallelize(Seq(3, 4, 5, 6, 7))

    rdd1.union(rdd2)
      .collect()
      .foreach(println(_))
  }

  /*
    差集
   */
  @Test
  def subtract() = {
    val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5))
    val rdd2 = sc.parallelize(Seq(3, 4, 5, 6, 7))

    rdd1.subtract(rdd2) //rdd1-rdd2
      .collect()
      .foreach(println(_))
  }

  /*
    只分组,不聚合
    groupByKey 运算结果格式:(Key, (value1, value2))
    reduceByKey 能不能在 Map 端做 Combiner:1.能不能减少IO
    groupByKey 在 Map端做 Combiner 没有意义
   */
  @Test
  def groupByKey() = {
    sc.parallelize(Seq(("a", 1), ("a", 1), ("c", 3)))
      .groupByKey()
      .collect()
      .foreach(println(_))  //只有一个参数打印输出可以简写
  }

  /*
    CombineByKey 算子中接受三个参数:
      转换数据的函数(初始函数,作用于第一条数据,用于开启整个计算),在分区上进行聚合,把所有分区的聚合结果聚合为最终结果

   */
  @Test
  def combineByKey() = {
    // 1.准备集合
    val rdd: RDD[(String, Double)] = sc.parallelize(Seq(
      ("铠", 100.0),
      ("耀", 99.0),
      ("镜", 99.0),
      ("镜", 98.0),
      ("铠", 97.0)
    ))
    // 2.算子操作
    //  2.1 createCombiner 转换数据
    //  2.2 mergeValue 分区上的聚合
    //  2.3 mergeCombiners 把分区上的结果再次聚合,生成最终结果
    val combineResult = rdd.combineByKey(
      createCombiner = (curr: Double) => (curr, 1),
      mergeValue = (curr: (Double, Int), nextValue: Double) => (curr._1 + nextValue, curr._2 + 1),
      mergeCombiners = (curr: (Double, Int), agg:(Double, Int)) => (curr._1 + agg._1, curr._2 + agg._2)
    )

    val resultRDD = combineResult.map( item => (item._1, item._2._1 / item._2._2))

    // 3. 输出数据
    resultRDD.collect().foreach(println(_))
  }

  /*
    foldByKey 和 reduceByKey 的区别是可以指定初始值
    foldByKey 和 Scala中的 foldLeft、foldRight 区别是,这个初始值作用于每一个数据
   */
  @Test
  def foldByKey() = {
    sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
      .foldByKey(10)((curr, agg) => curr + agg)
      .collect()
      .foreach(println(_))
  }

  /*
    aggregateByKey(zeroValue)(seqOp, combOp)
      zeroValue:指定初始值
      seqOp:作用于每个元素,根据初始值,进行计算
      combOp:将 seqOp 处理过的结果进行聚合

    aggregateByKey 比较适合针对每个数据要先处理,后聚合的场景
   */
  @Test
  def aggregateByKey() = {
    val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
    rdd.aggregateByKey(0.8)((zeroValue, item) => item * zeroValue, (curr, agg) => curr + agg)
      .collect()
      .foreach(println(_))
  }

  @Test
  def join() = {
    val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("b", 1)))
    val rdd2 = sc.parallelize(Seq(("a", 10), ("a", 11), ("b", 12)))
    rdd1.join(rdd2)
      .collect()
      .foreach(println(_))
  }


  /*
    sortBy 可以用于任何类型数据的RDD,sortByKey 只有 KV 类型数据的RDD中才有
    sortBy 可以按照任何部分顺序来排序,sortByKey 只能按照 Key 来排序
    sortByKey 写发简单,不用编写函数了
   */
  @Test
  def sort() = {
    val rdd1 = sc.parallelize(Seq(2, 4, 1, 5, 1, 8))
    val rdd2 = sc.parallelize(Seq(("a", 1), ("b", 3), ("c", 2)))

    println("-----------------------------")
    rdd1.sortBy(item => item).collect().foreach(println(_))
    println("-----------------------------")
    rdd2.sortBy(item => item._2).collect().foreach(println(_))
    println("-----------------------------")
    rdd2.sortByKey().collect().foreach(println(_))
  }


  /*
    repartition 进行重分区的时候,默认是 shuffle 的
    coalesce 进行重分区的时候,默认是不 shuffle 的,coalesce 默认不能增大分区数
   */
  @Test
  def partitioning() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4, 5), 2)

    println(rdd.repartition(5).partitions.size)
    println(rdd.repartition(1).partitions.size)
    println(rdd.coalesce(5, shuffle = true ).partitions.size)
  }

}

action 部分代码汇总

import org.apache.spark.{SparkConf, SparkContext}
import org.junit.Test

class ActionOp {

  val conf = new SparkConf().setMaster("local[6]").setAppName("action_op")
  val sc = new SparkContext(conf)

  /*
    需求:最终生成 (结果, price)
    注意:
      1. 函数中传入的 curr参数,并不是 Value,而是一整条数据
      2. reduce 整体上的结果,只有一个
   */
  @Test
  def reduce() = {
    val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
    val result: (String, Double) = rdd.reduce((curr, agg) => ("总价", curr._2 + agg._2))
    println(result) // reduce的结果是一个元组
  }


  /*
    RDD中自带的foreach算子,注意输出的结果顺序不一定按照原来Seq集合中的顺序
    是因为RDD是并行计算,异步操作
   */
  @Test
  def foreach() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4))
    rdd.foreach(item => println(item))
  }


  /*
    count 和 countByKey 的结果相距很远,每次调用 Action 都会生成一个 job,
    job 会运行获取结果,所以在俩个 job中间有大量的 Log,其实就是在启动job

    countByKey的运算结果是一个Map型数据:Map(a -> 2, b -> 1, c -> 1)

    数据倾斜:如果要解决数据倾斜,是不是要先知道谁倾斜,通过countByKey可以查看Key对应的
      数量,从而解决倾斜问题
   */
  @Test
  def count() = {
    val rdd = sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3), ("a", 4)))
    println(rdd.count())  // 求出集合中数据的总数
    println(rdd.countByKey()) // 得出 Key
    println(rdd.countByValue())
  }

  /*
    take() 和 takeSample() 都是获取数据,一个是直接获取,一个是采样获取(又放回、无放回)
    first:一般情况下,action 会从所有分区获取数据,相对来说速度比较慢,first 只是获取第一个元素
          所有只会处理第一个分区,所以速度很快,无需处理所有数据
   */
  @Test
  def take() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4, 5, 6))
    rdd.take(3).foreach(item => println(item))  // 返回前N个数据
    println(rdd.first())  // 返回第一个元素
    rdd.takeSample(withReplacement = false, num = 3).foreach(item => println(item))
  }

  // 等等数字运算...   注意对于数字类型的支持,都是Action
  @Test
  def numberic() = {
    val rdd = sc.parallelize(Seq(1, 2, 3, 4, 5, 10, 99, 120, 7))
    println(rdd.max())  // 最大值
    println(rdd.min())  // 最小值
    println(rdd.mean()) // 均值
    println(rdd.sum())  //求和
  }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值