Spark(二)-- SparkCore扩展 (二) -- RDD算子

目录

2.RDD算子

2.1 Transformations 算子

2.1.1 map: map(T ⇒ U)

2.1.2 flatMap(T ⇒ List[U])

2.1.3 filter(T ⇒ Boolean)

2.1.4 mapPartitions(List[T] ⇒ List[U])

2.1.5 mapPartitionsWithIndex

2.1.6 mapValues

2.1.7 sample(withReplacement, fraction, seed)

2.1.8 union(other)

2.1.9 intersection(other)

2.1.10 subtract(other, numPartitions)

2.1.11 distinct(numPartitions)

2.1.12 reduceByKey((V, V) ⇒ V, numPartition)

2.1.13 groupByKey()

2.1.14 combineByKey()

2.1.15 aggregateByKey()

2.1.16 foldByKey(zeroValue)((V, V) ⇒ V)

2.1.17 join(other, numPartitions)

2.1.18 cogroup(other, numPartitions)

2.1.19 cartesian(other)

2.1.20 sortBy(ascending, numPartitions)

2.1.21 partitionBy(partitioner)

2.1.22 coalesce(numPartitions)

2.1.23 repartition(numPartitions)

2.1.24 repartitionAndSortWithinPartitions

2.2 Action 算子

2.2.1 reduce( (T, T) ⇒ U )

2.2.2 collect()

2.2.3 count()

2.2.4 first()

2.2.5 take( N )

2.2.6 takeSample(withReplacement, fract)

2.2.7 fold(zeroValue)( (T, T) ⇒ U )

2.2.8 saveAsTextFile(path)

2.2.9 saveAsSequenceFile(path)

2.2.10 countByKey()

2.2.11 foreach( T ⇒ …​ )

2.3 转换算子和动作算子总结

2.4 RDD 对不同类型数据的支持

2.5 阶段练习和总结


2.RDD算子

目标

  1. 理解 RDD 的算子分类, 以及其特性

  2. 理解常见算子的使用

分类

RDD 中的算子从功能上分为两大类

  1. Transformation(转换) 它会在一个已经存在的 RDD 上创建一个新的 RDD, 将旧的 RDD 的数据转换为另外一种形式后放入新的 RDD(笔记:转换算子的本质其实就是生成各种rdd,让rdd之间具有联系,只是生成rdd链条,执行到转换的时候并不会执行整个程序,而是在Action被调用的时候,程序才会执行)

  2. Action(动作) 执行各个分区的计算任务, 将的到的结果返回到 Driver 中

RDD 中可以存放各种类型的数据, 那么对于不同类型的数据, RDD 又可以分为三类

  • 针对基础类型(例如 String)处理的普通算子

  • 针对 Key-Value 数据处理的 byKey 算子

  • 针对数字类型数据处理的计算算子

特点

  • Spark 中所有的 Transformations 是 Lazy(惰性) 的, 它们不会立即执行获得结果. 相反, 它们只会记录在数据集上要应用的操作. 只有当需要返回结果给 Driver 时, 才会执行这些操作, 通过 DAGScheduler 和 TaskScheduler 分发到集群中运行, 这个特性叫做 惰性求值

  • 默认情况下, 每一个 Action 运行的时候, 其所关联的所有 Transformation RDD 都会重新计算, 但是也可以使用 presist 方法将 RDD 持久化到磁盘或者内存中. 这个时候为了下次可以更快的访问, 会把数据保存到集群上.

2.1 Transformations 算子

2.1.1 map: map(T ⇒ U)

用法:

sc.parallelize(Seq(1, 2, 3))
  .map( num => num * 10 )
  .collect()

作用

  • 把 RDD 中的数据 一对一 的转为另一种形式

签名

  • def map[U: ClassTag](f: T ⇒ U): RDD[U]

参数

  • f → Map 算子是 原RDD → 新RDD 的过程, 传入函数的参数是原 RDD 数据, 返回值是经过函数转换的新 RDD 的数据

注意点

  • Map 是一对一, 如果函数是 String → Array[String] 则新的 RDD 中每条数据就是一个数组

2.1.2 flatMap(T ⇒ List[U])

用法:

sc.parallelize(Seq("Hello lily", "Hello lucy", "Hello tim"))
  .flatMap( line => line.split(" ") )
  .collect()

作用

  • FlatMap 算子和 Map 算子类似, 但是 FlatMap 是一对多

调用

  • def flatMap[U: ClassTag](f: T ⇒ List[U]): RDD[U]

参数

  • f → 参数是原 RDD 数据, 返回值是经过函数转换的新 RDD 的数据, 需要注意的是返回值是一个集合, 集合中的数据会被展平后再放入新的 RDD

注意点

  • flatMap 其实是两个操作, 是 map + flatten, 也就是先转换, 后把转换而来的 List 展开

  • Spark 中并没有直接展平 RDD 中数组的算子, 可以使用 flatMap 做这件事

2.1.3 filter(T ⇒ Boolean)

用法:

  /**
    * filter可以过滤掉数据集中一部分元素
    * filter中接收的函数,参数是每一个元素,如果这个函数返回true,当前元素会被加入,
    * 如果这个函数返回flase,当前元素会被过滤.
    */
  @Test
  def filter():Unit = {
    //1.定义集合
    //2.过滤数据
    //3.收集结果
    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

作用

  • Filter 算子的主要作用是过滤掉不需要的内容

2.1.4 mapPartitions(List[T] ⇒ List[U])

用法:

  @Test
  def mapPartitions2(): Unit = {
    //1.数据生成
    //2.算子使用
    //3.获取结果
    sc.parallelize(Seq(1, 2, 3, 4, 5, 6), 2)
      .mapPartitions(iter => {
        //遍历iter其中每一条数据进行转换,转换完成以后,返回这个iter
        //iter 是scala中的集合类型
        iter.map(iter => iter*10)
      })
      .collect()
      .foreach(x => print(x + " "))
  }
  //运行结果:10 20 30 40 50 60

作用:

  • RDD[T] ⇒ RDD[U] 和 map 类似, 但是针对整个分区的数据转换

2.1.5 mapPartitionsWithIndex

用法:

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

作用:

  • 和 mapPartitions 类似, 只是在函数中增加了分区的 Index

2.1.6 mapValues

用法:

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

作用

  • MapValues 只能作用于 Key-Value 型数据, 和 Map 类似, 也是使用函数按照转换数据, 不同点是 MapValues 只转换 Key-Value 中的 Value

2.1.7 sample(withReplacement, fraction, seed)

用法:

  /**
    * 作用: 把大数据集变小, 尽可能的减少数据集规律的损失
    * withReplacement: 指定为True的情况下, 可能重复, 如果是Flase, 无重复
    */
  @Test
  def sample(): Unit = {
    //1.定义集合
    //2.过滤数据
    //3.收集结果
    val rdd1 = sc.parallelize(Seq(1,2,3,4,5,6,7,8,9,10))
    val rdd2 = rdd1.sample(false,0.6)
    val result = rdd2.collect()
    result.foreach(item => println(item))
  }
  //运行结果:1 5 6 7 8

作用

  • Sample 算子可以从一个数据集中抽样出来一部分, 常用作于减小数据集以保证运行速度, 并且尽可能少规律的损失

参数

  • Sample 接受第一个参数为`withReplacement`, 意为是否取样以后是否还放回原数据集供下次使用, 简单的说, 如果这个参数的值为 true, 则抽样出来的数据集中可能会有重复

  • Sample 接受第二个参数为`fraction`, 意为抽样的比例

  • Sample 接受第三个参数为`seed`, 随机数种子, 用于 Sample 内部随机生成下标, 一般不指定, 使用默认值

2.1.8 union(other)

用法:

  /**
    * 并集 可以重复的
    */
  @Test
  def union(): Unit={
    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

作用:

  • 取并集

2.1.9 intersection(other)

用法:

  /**
    * 交集 3,4,5
    */
  @Test
  def intersection(): Unit={
    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(_))
  }
  //运行结果:3,4,5

作用

  • Intersection 算子是一个集合操作, 用于求得 左侧集合 和 右侧集合 的交集, 换句话说, 就是左侧集合和右侧集合都有的元素, 并生成一个新的 RDD

2.1.10 subtract(other, numPartitions)

用法:

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

    rdd1.subtract(rdd2)
      .collect()
      .foreach(println(_))
  }
  //运行结果:1,2

作用:

  • (RDD[T], RDD[T]) ⇒ RDD[T] 差集, 可以设置分区数

2.1.11 distinct(numPartitions)

用法:

sc.parallelize(Seq(1, 1, 2, 2, 3))
  .distinct()
  .collect()

作用

  • Distinct 算子用于去重

注意点

  • Distinct 是一个需要 Shuffled 的操作

  • 本质上 Distinct 就是一个 reductByKey, 把重复的合并为一个

2.1.12 reduceByKey((V, V) ⇒ V, numPartition)

用法:

sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
  .reduceByKey( (curr, agg) => curr + agg )
  .collect()

作用

  • 首先按照 Key 分组生成一个 Tuple, 然后针对每个组执行 reduce 算子

调用

  • def reduceByKey(func: (V, V) ⇒ V): RDD[(K, V)]

参数

  • func → 执行数据处理的函数, 传入两个参数, 一个是当前值, 一个是局部汇总, 这个函数需要有一个输出, 输出就是这个 Key 的汇总结果

注意点

  • ReduceByKey 只能作用于 Key-Value 型数据, Key-Value 型数据在当前语境中特指 Tuple2

  • ReduceByKey 是一个需要 Shuffled 的操作

  • 和其它的 Shuffled 相比, ReduceByKey是高效的, 因为类似 MapReduce 的, 在 Map 端有一个 Cominer, 这样 I/O 的数据便会减少

2.1.13 groupByKey()

用法:

sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
  .groupByKey()
  .collect()

作用

  • GroupByKey 算子的主要作用是按照 Key 分组, 和 ReduceByKey 有点类似, 但是 GroupByKey 并不求聚合, 只是列举 Key 对应的所有 Value

注意点

  • GroupByKey 是一个 Shuffled

  • GroupByKey 和 ReduceByKey 不同, 因为需要列举 Key 对应的所有数据, 所以无法在 Map 端做 Combine, 所以 GroupByKey 的性能并没有 ReduceByKey 好

2.1.14 combineByKey()

用法:

val rdd = sc.parallelize(Seq(
  ("zhangsan", 99.0),
  ("zhangsan", 96.0),
  ("lisi", 97.0),
  ("lisi", 98.0),
  ("zhangsan", 97.0))
)

val combineRdd = rdd.combineByKey(
  score => (score, 1),
  (scoreCount: (Double, Int),newScore) => (scoreCount._1 + newScore, scoreCount._2 + 1),
  (scoreCount1: (Double, Int), scoreCount2: (Double, Int)) =>
    (scoreCount1._1 + scoreCount2._1, scoreCount1._2 + scoreCount2._2)
)

val meanRdd = combineRdd.map(score => (score._1, score._2._1 / score._2._2))

meanRdd.collect()

作用

  • 对数据集按照 Key 进行聚合

调用

  • combineByKey(createCombiner, mergeValue, mergeCombiners, [partitioner], [mapSideCombiner], [serializer])

参数

  • createCombiner 将 Value 进行初步转换

  • mergeValue 在每个分区把上一步转换的结果聚合

  • mergeCombiners 在所有分区上把每个分区的聚合结果聚合

  • partitioner 可选, 分区函数

  • mapSideCombiner 可选, 是否在 Map 端 Combine

  • serializer 序列化器

注意点

  • combineByKey 的要点就是三个函数的意义要理解

  • groupByKeyreduceByKey 的底层都是 combineByKey

2.1.15 aggregateByKey()

用法:

val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.aggregateByKey(0.8)(
  seqOp = (zero, price) => price * zero,
  combOp = (curr, agg) => curr + agg
).collect()
println(result)

作用

  • 聚合所有 Key 相同的 Value, 换句话说, 按照 Key 聚合 Value

调用

  • rdd.aggregateByKey(zeroValue)(seqOp, combOp)

参数

  • zeroValue 初始值

  • seqOp 转换每一个值的函数  (作用于每一条数据)

  • comboOp 将转换过的值聚合的函数  (把整体聚合,生成最终的结果)

注意点 * 为什么需要两个函数? aggregateByKey 运行将一个`RDD[(K, V)]聚合为`RDD[(K, U)], 如果要做到这件事的话, 就需要先对数据做一次转换, 将每条数据从`V`转为`U`, `seqOp`就是干这件事的 ** 当`seqOp`的事情结束以后, `comboOp`把其结果聚合

  • 和 reduceByKey 的区别::

    • aggregateByKey 最终聚合结果的类型和传入的初始值类型保持一致

    • reduceByKey 在集合中选取第一个值作为初始值, 并且聚合过的数据类型不能改变

2.1.16 foldByKey(zeroValue)((V, V) ⇒ V)

用法:

sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
  .foldByKey(zeroValue = 10)( (curr, agg) => curr + agg )
  .collect()

作用

  • 和 ReduceByKey 是一样的, 都是按照 Key 做分组去求聚合, 但是 FoldByKey 的不同点在于可以指定初始值

调用

foldByKey(zeroValue)(func)

参数

  • zeroValue 初始值

  • func seqOp 和 combOp 相同, 都是这个参数

注意点

  • FoldByKey 是 AggregateByKey 的简化版本, seqOp 和 combOp 是同一个函数

  • FoldByKey 指定的初始值作用于每一个 Value

2.1.17 join(other, numPartitions)

作用:

val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("b", 1)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("a", 11), ("a", 12)))

rdd1.join(rdd2).collect()

作用

  • 将两个 RDD 按照相同的 Key 进行连接

调用

join(other, [partitioner or numPartitions])

参数

  • other 其它 RDD

  • partitioner or numPartitions 可选, 可以通过传递分区函数或者分区数量来改变分区

注意点

  • Join 有点类似于 SQL 中的内连接, 只会再结果中包含能够连接到的 Key

  • Join 的结果是一个笛卡尔积形式, 例如`"a", 1), ("a", 2"a", 10), ("a", 11的 Join 结果集是 `"a", 1, 10), ("a", 1, 11), ("a", 2, 10), ("a", 2, 11)

2.1.18 cogroup(other, numPartitions)

用法:

val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("a", 5), ("b", 2), ("b", 6), ("c", 3), ("d", 2)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("b", 1), ("d", 3)))
val rdd3 = sc.parallelize(Seq(("b", 10), ("a", 1)))

val result1 = rdd1.cogroup(rdd2).collect()
val result2 = rdd1.cogroup(rdd2, rdd3).collect()

/*
执行结果:
Array(
  (d,(CompactBuffer(2),CompactBuffer(3))),
  (a,(CompactBuffer(1, 2, 5),CompactBuffer(10))),
  (b,(CompactBuffer(2, 6),CompactBuffer(1))),
  (c,(CompactBuffer(3),CompactBuffer()))
)
 */
println(result1)

/*
执行结果:
Array(
  (d,(CompactBuffer(2),CompactBuffer(3),CompactBuffer())),
  (a,(CompactBuffer(1, 2, 5),CompactBuffer(10),CompactBuffer(1))),
  (b,(CompactBuffer(2, 6),CompactBuffer(1),Co...
 */
println(result2)

作用

  • 多个 RDD 协同分组, 将多个 RDD 中 Key 相同的 Value 分组

调用

  • cogroup(rdd1, rdd2, rdd3, [partitioner or numPartitions])

参数

  • rdd…​ 最多可以传三个 RDD 进去, 加上调用者, 可以为四个 RDD 协同分组

  • partitioner or numPartitions 可选, 可以通过传递分区函数或者分区数来改变分区

注意点

  • 对 RDD1, RDD2, RDD3 进行 cogroup, 结果中就一定会有三个 List, 如果没有 Value 则是空 List, 这一点类似于 SQL 的全连接, 返回所有结果, 即使没有关联上

  • CoGroup 是一个需要 Shuffled 的操作

2.1.19 cartesian(other)

(RDD[T], RDD[U]) ⇒ RDD[(T, U)] 生成两个 RDD 的笛卡尔积

2.1.20 sortBy(ascending, numPartitions)

用法:

val rdd1 = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val sortByResult = rdd1.sortBy( item => item._2 ).collect()
val sortByKeyResult = rdd1.sortByKey().collect()

println(sortByResult)
println(sortByKeyResult)

作用

  • 排序相关相关的算子有两个, 一个是`sortBy`, 另外一个是`sortByKey`

调用

sortBy(func, ascending, numPartitions)

参数

  • `func`通过这个函数返回要排序的字段

  • `ascending`是否升序

  • `numPartitions`分区数

注意点

  • 普通的 RDD 没有`sortByKey`, 只有 Key-Value 的 RDD 才有

  • `sortBy`可以指定按照哪个字段来排序, `sortByKey`直接按照 Key 来排序

2.1.21 partitionBy(partitioner)

使用用传入的 partitioner 重新分区, 如果和当前分区函数相同, 则忽略操作

2.1.22 coalesce(numPartitions)

用法:减少分区数

val rdd = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val oldNum = rdd.partitions.length

val coalesceRdd = rdd.coalesce(4, shuffle = true)
val coalesceNum = coalesceRdd.partitions.length

val repartitionRdd = rdd.repartition(4)
val repartitionNum = repartitionRdd.partitions.length

print(oldNum, coalesceNum, repartitionNum)

作用

  • 一般涉及到分区操作的算子常见的有两个, repartitioin 和 coalesce, 两个算子都可以调大或者调小分区数量

调用

  • repartitioin(numPartitions)

  • coalesce(numPartitions, shuffle)

参数

  • numPartitions 新的分区数

  • shuffle 是否 shuffle, 如果新的分区数量比原分区数大, 必须 Shuffled, 否则重分区无效

注意点

  • repartition 和 coalesce 的不同就在于 coalesce 可以控制是否 Shuffle

  • repartition 是一个 Shuffled 操作

2.1.23 repartition(numPartitions)

重新分区

2.1.24 repartitionAndSortWithinPartitions

重新分区的同时升序排序, 在partitioner中排序, 比先重分区再排序要效率高, 建议使用在需要分区后再排序的场景使用

 

2.2 Action 算子

2.2.1 reduce( (T, T) ⇒ U )

用法:

val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.reduce((curr, agg) => ("总价", curr._2 + agg._2))
println(result)

作用

  • 对整个结果集规约, 最终生成一条数据, 是整个数据集的汇总

调用

  • reduce( (currValue[T], agg[T]) ⇒ T )

注意点

  • reduce 和 reduceByKey 是完全不同的, reduce 是一个 action, 并不是 Shuffled 操作

  • 本质上 reduce 就是现在每个 partition 上求值, 最终把每个 partition 的结果再汇总

笔记

reduceByKey和reduce有什么区别?

1.reduce是一个Action算子,redueceByKey是一个转换算子

2.假设一个RDD里面有1万条数据,大部分key相同,有10个不同的key

rdd.reduceByKey生成10条数据

rdd.reduce生成1条结果

reduceByKey本质上是先按照Key分组,然后把每组聚合

reduce是针对于一整个数据集来进行聚合

3.reduceByKey是针对KV型数据来进行计算,reduce可以针对所有类型的数据进行计算

 

reduce算子是一个Shuffle操作吗?

*Shuffle操作分为mapper和reduce,mapper将数据放入paritioner的函数计算,求得分往哪一个reducer,再分到对应的reducer中(多对多

*reduce操作并没有mapper和reducer,因为reduce算子会作用于RDD中的每一个分区,然后再分区上求得局部结果,最终汇总到Driver中求得最终结果(多对一

RDD中有5大属性,Partitioner再Shuffe过程中使用

Partitioner只有KV型的RDD才有

 

2.2.2 collect()

以数组的形式返回数据集中所有元素

2.2.3 count()

返回元素个数

2.2.4 first()

返回第一个元素

2.2.5 take( N )

返回前 N 个元素

2.2.6 takeSample(withReplacement, fract)

类似于 sample, 区别在这是一个Action, 直接返回结果

2.2.7 fold(zeroValue)( (T, T) ⇒ U )

指定初始值和计算函数, 折叠聚合整个数据集

2.2.8 saveAsTextFile(path)

将结果存入 path 对应的文件中

2.2.9 saveAsSequenceFile(path)

将结果存入 path 对应的 Sequence 文件中

2.2.10 countByKey()

用法:

val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.countByKey()
println(result)

作用

  • 求得整个数据集中 Key 以及对应 Key 出现的次数

注意点

  • 返回结果为 Map(key → count)

  • 常在解决数据倾斜问题时使用, 查看倾斜的 Key

2.2.11 foreach( T ⇒ …​ )

遍历每一个元素

 

应用

val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
// 结果: Array((手机,10.0), (手机,15.0), (电脑,20.0))
println(rdd.collect())
// 结果: Array((手机,10.0), (手机,15.0))
println(rdd.take(2))
// 结果: (手机,10.0)
println(rdd.first())

2.3 转换算子和动作算子总结

RDD 的算子大部分都会生成一些专用的 RDD

  • mapflatMapfilter 等算子会生成 MapPartitionsRDD

  • coalescerepartition 等算子会生成 CoalescedRDD

常见的 RDD 有两种类型

  • 转换型的 RDD, Transformation

  • 动作型的 RDD, Action

常见的 Transformation 类型的 RDD

  • map

  • flatMap

  • filter

  • groupBy

  • reduceByKey

常见的 Action 类型的 RDD

  • collect

  • countByKey

  • reduce

 

2.4 RDD 对不同类型数据的支持

目标

  1. 理解 RDD 对 Key-Value 类型的数据是有专门支持的

  2. 理解 RDD 对数字类型也有专门的支持

一般情况下 RDD 要处理的数据有三类

  • 字符串

  • 键值对

  • 数字型

RDD 的算子设计对这三类不同的数据分别都有支持

  • 对于以字符串为代表的基本数据类型是比较基础的一些的操作, 诸如 map, flatMap, filter 等基础的算子

  • 对于键值对类型的数据, 有额外的支持, 诸如 reduceByKey, groupByKey 等 byKey 的算子

  • 同样对于数字型的数据也有额外的支持, 诸如 max, min 等

RDD 对键值对数据的额外支持

键值型数据本质上就是一个二元元组, 键值对类型的 RDD 表示为 RDD[(K, V)]

RDD 对键值对的额外支持是通过隐式支持来完成的, 一个 RDD[(K, V)], 可以被隐式转换为一个 PairRDDFunctions 对象, 从而调用其中的方法.

3b365c28403495cb8d07a2ee5d0a6376

既然对键值对的支持是通过 PairRDDFunctions 提供的, 那么从 PairRDDFunctions 中就可以看到这些支持有什么

类别算子

聚合操作

reduceByKey

foldByKey

combineByKey

分组操作

cogroup

groupByKey

连接操作

join

leftOuterJoin

rightOuterJoin

排序操作

sortBy

sortByKey

Action

countByKey

take

collect

RDD 对数字型数据的额外支持

对于数字型数据的额外支持基本上都是 Action 操作, 而不是转换操作

算子含义

count

个数

mean

均值

sum

求和

max

最大值

min

最小值

variance

方差

sampleVariance

从采样中计算方差

stdev

标准差

sampleStdev

采样的标准差

val rdd = sc.parallelize(Seq(1, 2, 3))
// 结果: 3
println(rdd.max())

2.5 阶段练习和总结

导读

1.通过本节, 希望大家能够理解 RDD 的一般使用步骤

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

class StagePractice {

  @Test
  def pmProcess(): Unit ={
    //1.创建sc对象
    val conf = new SparkConf().setMaster("local[6]").setAppName("stage_practice")
    val sc = new SparkContext(conf)

    //2.读取文件
    val source = sc.textFile("dataset/BeijingPM20100101_20151231_noheader.csv")

    //3.通过算子处理数据
    //  1.抽取数据 年 月 PM 返回结果:((年,月), PM)
    //  2.清洗,过滤掉空的字符串,过滤掉NA
    //  3.聚合
    //  4.排序
    val resultRDD = source.map( x => ((x.split(",")(1),x.split(",")(2)),x.split(",")(6)))
      .filter( x => StringUtils.isNotEmpty(x._2) && !x._2.equalsIgnoreCase("NA"))
      .map( x => (x._1,x._2.toInt))
      .reduceByKey( (curr,agg) => curr + agg)
      .sortBy( x => x._2, ascending = false)

    //4.获取结果
    resultRDD.take(10).foreach(x => println(x))

    //5.运行测试
    sc.stop()

  }
}

通过上述代码可以看到, 其实 RDD 的整体使用步骤如下

20190518105630

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值