RDD 中的函数

RDD(Resilient Distributed Dataset)

Spark 的核心是 RDD,即弹性分布数据集,通过 RDD 我们可以像操作本地集合一样以函数式编程的方式操作 RDD 这个分布式数据集,进行各种并行计算,RDD 中很多处理数据函数与列表 List 中相同与类似

RDD 的操作(函数、算子)分类

RDD Programming Guide - Spark 3.1.2 Documentation (apache.org)

① Transformation(转换操作)

返回一个新的 RDD,所有 Transformation 函数都是 Lazy,不会立即执行,需要 Action 函数触发

② Action(动作操作)

返回值不是 RDD(无返回值或返回其他的),所有 Action函数立即执行(Eager),比如count、first、collect、take等

Transformation 函数

在这里插入图片描述
在这里插入图片描述

转换含义
map(func)返回一个新的 RDD,该 RDD 由每一个输入元素经过 func 函数转换后组成
filter(func)返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成
flatMap(func)类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
mapPartitions(func)类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]
mapPartitionsWithIndex(func)类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是 (Int, Interator[T]) => Iterator[U]
sample(withReplacement, fraction, seed)根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子
union(otherDataset)对源 RDD 和参数 RDD 求并集后返回一个新的 RDD
intersection(otherDataset)对源RDD和参数RDD求交集后返回一个新的RDD
distinct([numTasks]))对源RDD进行去重后返回一个新的RDD
groupByKey([numTasks])在一个(K,V)的 RDD 上调用,返回一个(K, Iterator[V])的 RDD
reduceByKey(func, [numTasks])在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce 任务的个数可以通过第二个可选的参数来设置
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])
sortByKey([ascending], [numTasks])在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
sortBy(func,[ascending], [numTasks])与sortByKey类似,但是更灵活
join(otherDataset, [numTasks])在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
cogroup(otherDataset, [numTasks])在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
cartesian(otherDataset)笛卡尔积
pipe(command, [envVars])对 RDD 进行管道操作
coalesce(numPartitions)减少 RDD 的分区数到指定值。在过滤大量数据之后,可以执行此操作
repartition(numPartitions)重新给 RDD 分区

Action 函数

在这里插入图片描述

动作含义
reduce(func)通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的
collect()在驱动程序中,以数组的形式返回数据集的所有元素
count()返回RDD的元素个数
first()返回RDD的第一个元素(类似于take(1))
take(n)返回一个由数据集的前n个元素组成的数组
takeSample(withReplacement,num, [seed])返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子
takeOrdered(n, [ordering])返回自然顺序或者自定义顺序的前 n 个元素
saveAsTextFile(path)将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
saveAsSequenceFile(path)将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
saveAsObjectFile(path)将数据集的元素,以 Java 序列化的方式保存到指定的目录下
countByKey()针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
foreach(func)在数据集的每一个元素上,运行函数func进行更新。
foreachPartition(func)在数据集的每一个分区上,运行函数func

基本函数

map 函数

// 格式:表示将 RDD 经由某一函数 f 后,转变为另一个RDD
map(f:T=>U) : RDD[T]=>RDD[U]
// 示例:创建一个集合 [1,2,3,4],给每个集合的元素 +1,最终结果为 [2,3,4,5]
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
 * @author Kyle White
 * @date Aug 18,2021
 */
object DemoTest {

  def main(args: Array[String]): Unit = {

    // 创建 SparkContext
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName.stripPrefix("$")).setMaster("local[2]")
    val sc: SparkContext = new SparkContext(conf)
    sc.setLogLevel("WARN")

    // 创建序列并将其转换为 RDD
    val seq: Seq[Int] = Seq(1, 2, 3, 4)
    val inputRDD: RDD[Int] = sc.parallelize(seq, numSlices = 2)
    val result: RDD[Int] = inputRDD.map(_ + 1)

    // 查看结果
    println(result.collect().toBuffer)

    // 关闭 SparkContext
    sc.stop()

  }

}

在这里插入图片描述
flatMap 函数

先将集合中的数据 map 操作,然后将数据扁平化

// 格式:表示将 RDD 经由某一函数 f 后,转变为一个新的 RDD,但是与 map 不同,RDD 中的每一个元素会被映射成新的 0 到多个元素(f 函数返回的是一个序列 Seq)
flatMap(f:T=>Seq[U]) : RDD[T]=>RDD[U])
// 将集合 ["Hello World","Paris London","Kyle Jack"] 转换为 ["Hello","World","Paris","London","Kyle","Jack"]

// 使用 Scala 中集合 Seq 序列
val seq: Seq[String] = Seq("Hello World", "Hello Hadoop", "Hello Spark")
// 将 Scala 集合转换为 RDD(并行化)
val inputRDD:RDD[String] = sc.parallelize(seq, numSlices = 2)
val result:RDD[String] = inputRDD.flatMap(_.split(" "))

filter 函数

// 格式:表示将 RDD 经由某一函数 f 后,只保留 f 返回为 true 的数据,组成新的 RDD
filter(f:T=>Bool) : RDD[T]=>RDD[T]
// 过滤出偶数
val seq: Seq[Int] = Seq(1, 2, 3, 4)
val inputRDD: RDD[Int] = sc.parallelize(seq, numSlices = 2)
val result: RDD[Int] = inputRDD.filter(_.%(2)==0)

在这里插入图片描述
foreach 函数

// 格式:将函数 func 应用在数据集的每一个元素上,通常用于更新一个累加器,或者和外部存储系统进行交互
foreach(func)

saveAsTextFile 函数

// 格式:数据集内部的元素会调用其 toString 方法,转换为字符串形式,然后根据传入的路径保存成文本文件,既可以是本地文件系统,也可以是HDFS 等
saveAsTextFile(path:String)

分区操作函数

每个 RDD 由多分区组成的,实际开发建议对每个分区数据的进行操作

例如:map 函数使用 mapPartitions 代替、foreach 函数使用 foreachPartition 代替

// 示例一:对每个分区进行 map 操作
val result: RDD[(String, Int)] = inputRDD
  .flatMap(_.trim.split(" "))
  .mapPartitions { iter =>
    iter.map((_, 1))
  }
// 示例二:对每个分区数据遍历
result.foreachPartition{ iter =>
  // 获取各个分区ID
  val partitionId: Int = TaskContext.getPartitionId()
  iter.foreach{ case (key, value) =>
    println(s"p-${partitionId}: key = $key, value = $value")
  }
}

重分区函数

对 RDD 中分区数目进行调整(增加分区或减少分区),有以下几个函数

① 增加分区函数

注意:此函数使用时会产生 shuffle ,使用时需谨慎

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

② 减少分区函数

  def coalesce(numPartitions: Int, shuffle: Boolean = false): RDD[T] = withScope {}

③ 调整分区函数

注意:此函数需要通过传递 分区器(Partitioner) 来改变 RDD 的分区数据

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

聚合函数

reduce 函数

def reduce(f: (T,T) => T): T 
// 示例:求列表中 List 中元素之和,RDD 分区数目为 2
val inputRDD: RDD[Int] = sc.parallelize(1 to 10, numSlices = 2)
val result: Int = inputRDD.reduce(_ + _)

fold 聚合函数(可以设置初始值)

def fold(zeroValue: T)(op: (T,T)): T
// 示例:求列表中 List 中元素之和,RDD 分区数目为 2
val inputRDD: RDD[Int] = sc.parallelize(1 to 10, numSlices = 2)
val result: Int = inputRDD.fold(0)(_ + _)

aggregate 高级聚合函数

def aggregate[U: ClassTag](zeroValue: U) // 聚合中间临时变量初始值
(
   // 分区内聚合函数
   seqOp: (U, T) => U, 
  // 分区间聚合函数
   combOp: (U, U) => U
  
): U = withScope {
  // Clone the zero value since we will also be serializing it as part of tasks
  // ...
}

① seqOp 函数的第一个参数是累加器,第一次执行时,会把 zeroValue 赋给累加器。第一次之后会把返回值赋给累加器,作为下一次运算的第一个参数

② seqOP 函数每个分区下的每个 key 有个累加器,combOp 函数全部分区有几个 key 就有几个累加器。如果某个 key 只存在于一个分区下,不会对他执行 combOp 函数

// 示例:求 1-10 最大的两个值 
val dataRDD: RDD[Int] = sc.parallelize(1 to 10, numSlices = 2)

val top2: mutable.Seq[Int] = dataRDD.aggregate(new ListBuffer[Int]())(
  // 分区内聚合函数,每个分区内数据如何聚合  seqOp: (U, T) => U,
  (u, t) => {
    println(s"p-${TaskContext.getPartitionId()}: u = $u, t = $t")
    u += t 
    val top = u.sorted.takeRight(2)
    top
  },
  // 分区间聚合函数,每个分区聚合的结果如何聚合 combOp: (U, U) => U
  (u1, u2) => {
    println(s"p-${TaskContext.getPartitionId()}: u1 = $u1, u2 = $u2")
    u1 ++= u2
    u1.sorted.takeRight(2)
  }
)

PairRDDFunctions 聚合函数

Spark 中有一个 object 对象 PairRDDFunctions ,主要针对 RDD 的数据类型是 Key/Value 对的数据提供函数,方便数据分析处理

① 分组函数 groupByKey

将相同的 keyvalue 组合在一起,所有的 value 存储在 Iterable

# 此函数可能出现的问题
1.数据倾斜
当某个 key 对应的 value 的值非常多的时候,迭代器中的数据会非常多
2.OOM(内存溢出)
综合:在 SparkCore 开发中,能不用 groupByKey 就尽量别用
val linesSeq: Seq[String] = Seq("hello world test", "java python scala", "jack kyle", "hello")
val inputRDD: RDD[String] = sc.parallelize(linesSeq, numSlices = 2)
val wordsRDD: RDD[(String, Int)] = inputRDD.flatMap(_.split(" ")).map((_, 1))

val wordsGroupRDD: RDD[(String, Iterable[Int])] = wordsRDD.groupByKey()
val resultRDD: RDD[(String, Int)] = wordsGroupRDD.mapValues(_.sum)

② 分组聚合函数 reduceByKey 和 foldByKey

val linesSeq: Seq[String] = Seq("hello world test", "java python scala", "jack kyle", "hello")
val inputRDD: RDD[String] = sc.parallelize(linesSeq, numSlices = 2)
val wordsRDD: RDD[(String, Int)] = inputRDD.flatMap(_.split(" ")).map((_, 1))

val resultRDD: RDD[(String, Int)] = wordsRDD.reduceByKey(_ + _)

③ 分组聚合函数 aggregateByKey

val linesSeq: Seq[String] = Seq("hello world test", "java python scala", "jack kyle", "hello")
val inputRDD: RDD[String] = sc.parallelize(linesSeq, numSlices = 2)
val wordsRDD: RDD[(String, Int)] = inputRDD.flatMap(_.split(" ")).map((_, 1))

val resultRDD = wordsRDD.aggregateByKey(0)(
  (tmp: Int, item: Int) => {
    tmp + item
  },
  (tmp: Int, result: Int) => {
    tmp + result
  }
)

groupByKey 和 reduceByKey 的区别

① 返回值类型不同

reduceByKey 返回的是 RDD[(K, V)],而 groupByKey 返回的是 RDD[(K, Iterable[V])]

② 过程不同

reduceByKey 先分区内聚合然后最后再做一次聚合,groupByKey 先分区内分组,最后再做聚合,IO 比较高
在这里插入图片描述
在这里插入图片描述

关联函数

当两个 RDD 的数据类型为二元组 Key/Value时,可以依据 Key 进行关联 Join

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

def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]

def rightOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (Option[V], W))]

在这里插入图片描述
在这里插入图片描述

// 示例
// 模拟数据集
val empRDD: RDD[(Int, String)] = sc.parallelize(
  Seq((1001, "Kyle"), (1002, "Jack"), (1003, "Lucy"), (1004, "Sam"))
)
val deptRDD: RDD[(Int, String)] = sc.parallelize(
  Seq((1001, "tec"), (1002, "fac"))
)

val joinRDD: RDD[(Int, (String, String))] = empRDD.join(deptRDD)
println(joinRDD.collectAsMap())

val leftJoinRDD: RDD[(Int, (String, Option[String]))] = empRDD.leftOuterJoin(deptRDD)
println(leftJoinRDD.collectAsMap())

在这里插入图片描述

关联函数

当两个 RDD 的数据类型为二元组 Key/Value时,可以依据 Key 进行关联 Join

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

def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]

def rightOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (Option[V], W))]
// 示例
// 模拟数据集
val empRDD: RDD[(Int, String)] = sc.parallelize(
  Seq((1001, "Kyle"), (1002, "Jack"), (1003, "Lucy"), (1004, "Sam"))
)
val deptRDD: RDD[(Int, String)] = sc.parallelize(
  Seq((1001, "tec"), (1002, "fac"))
)

val joinRDD: RDD[(Int, (String, String))] = empRDD.join(deptRDD)
println(joinRDD.collectAsMap())

val leftJoinRDD: RDD[(Int, (String, Option[String]))] = empRDD.leftOuterJoin(deptRDD)
println(leftJoinRDD.collectAsMap())

排序函数

sortByKey

针对 RDD 中数据类型 key/value 对 时,按照 Key 进行排序

val inputRdd: RDD[String] = sc.parallelize(Seq("hello world test", "java python scala", "jack scala", "hello"))

// sortByKey
val sortedRDD: RDD[(Int, String)] = inputRdd.flatMap(_.split("\\s+"))
  .map((_, 1))
  .reduceByKey(_ + _)
  .map(_.swap) // 交换
  .sortByKey(ascending = false) // 降序

sortedRDD.take(3).foreach(println) // 只取前 3

在这里插入图片描述

sortBy

针对 RDD 中数据指定排序规则

val inputRdd: RDD[String] = sc.parallelize(Seq("hello world test", "java python scala", "jack scala", "hello"))

val sortedByRDD:RDD[(String, Int)]  = inputRdd.flatMap(_.split("\\s+"))
  .map((_, 1))
  .reduceByKey(_ + _)
  .sortBy(_._2, ascending = false)  // 降序
sortedByRDD.take(3).foreach(println)

在这里插入图片描述

top

按照 RDD 中数据采用降序方式排序,如果是 Key/Value对,按照 Key 降序排序

val inputRdd: RDD[String] = sc.parallelize(Seq("hello world test", "java python scala", "jack scala", "hello"))

val topRDD:Array[(String,Int)] = inputRdd.flatMap(_.split("\\s+"))
  .map((_, 1))
  .reduceByKey(_ + _)
  .top(3)(Ordering.by(_._2))
topRDD.foreach(println)

在这里插入图片描述

交集、并集、差集

并集

// 初始化数据
val seq1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4))
val seq2: RDD[Int] = sc.parallelize(List(5, 6, 3, 4))

// 并集
val unionResult: RDD[Int] = seq1.union(seq2)
println(unionResult.collect().toBuffer)

在这里插入图片描述
交集

// 初始化数据
val seq1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4))
val seq2: RDD[Int] = sc.parallelize(List(5, 6, 3, 4))

val intersectionResult:RDD[Int] = seq1.intersection(seq2)
println(intersectionResult.collect().toBuffer)

在这里插入图片描述

差集

// 初始化数据
val seq1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4))
val seq2: RDD[Int] = sc.parallelize(List(5, 6, 3, 4))

val subtractResult1: RDD[Int] = seq1.subtract(seq2)
println(subtractResult1.collect().toBuffer)
val subtractResult2: RDD[Int] = seq2.subtract(seq1)
println(subtractResult2.collect().toBuffer)

在这里插入图片描述
笛卡尔积

// 笛卡尔积
val seq3: RDD[String] = sc.parallelize(List("dept1", "dept2"))
val seq4: RDD[String] = sc.parallelize(List("Jack", "Sam", "Lisa"))
val cartesianResult: RDD[(String, String)] = seq3.cartesian(seq4)
println(cartesianResult.collect().toBuffer)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值