RDD算子分为两大类
转换算子:功能的补充和封装,将旧的RDD包装成新的RDD
例如 flatMap,map
行动算子:触发任务的调度和作业的执行
例如collect
RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value类型
def map[U: ClassTag](f: T => U): RDD[U]
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换
/**
* map
* 1,2,3,4 =>2,4,6,8
*1.rdd的计算一个分区的数据是一个一个执行逻辑
* 只有前面一个数据全部的逻辑执行完毕后,才会执行下一个数据
* 分区内数据的执行是有序的
* 2.不同分区数据计算是无序的
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
rdd.map(_*2).collect.foreach(x=>print(x+"\t"))
def mapPartitions[U: ClassTag]( f: Iterator[T] => Iterator[U], preservesPartitioning: Boolean = false): RDD[U]
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据
/**
* mapPartitions
* 可以以分区为单位进行数据转换操作
* 但是会将整个分区的数据加载到内存进行引用
* 如果处理完的数据是不会被释放掉,存在对象引用
* 若遇到内存小,数据量大则会出现内存以内存溢出
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
val mapRdd: RDD[Int] = rdd.mapPartitions(
iter => {
println(">>>>>>>")
iter.map(_ * 2)
}
)
mapRdd.collect().foreach(println)
def mapPartitionsWithIndex[U: ClassTag](f: (Int, Iterator[T]) => Iterator[U],preservesPartitioning: Boolean = false): RDD[U]
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引
/**
* mapPartitionsWithIndex
* 获取第二个分区内的数据
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
val mapRdd: RDD[Int] = rdd.mapPartitionsWithIndex(
(index, iter) => {
if (index == 1) {
//保留迭代器
iter
} else {
//Nil为空集合---即为空迭代器
Nil.iterator
}
}
)
mapRdd.collect().foreach(println)
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
/**
* flatMap 扁平映射
*
*/
val rdd: RDD[List[Int]] = sc.makeRDD(List(List(1, 2), List(3, 4)))
val flatRdd: RDD[Int] = rdd.flatMap(
list => {
list
}
)
flatRdd.collect().foreach(println)
def glom(): RDD[Array[T]]
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
/**
* glom
* List =>Int
* Int =>Array
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
val glomRdd: RDD[Array[Int]] = rdd.glom()
glomRdd.collect().foreach(x=>println(x.mkString(",")))
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle
极限情况下,数据可能被分在同一个分区中
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
/**
* groupBy
* 将数据源中每个数据进行分组判断,根据返回分组的key进行分组
* 相同的key值的数据就会放在一个组中
*
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
val groupRdd: RDD[(Int, Iterable[Int])] = rdd.groupBy(_ % 2)
groupRdd.collect().foreach(println)
def filter(f: T => Boolean): RDD[T]
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃
当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜
/**
* filter
* 奇数留下,偶数去除
*
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val filterRdd: RDD[Int] = rdd.filter(_ % 2 != 0)
filterRdd.collect().foreach(println)
def sample(withReplacement: Boolean,fraction: Double, seed: Long = Utils.random.nextLong): RDD[T]
根据指定的规则从数据集中抽取数据
/**
* sample
* 抽样
* 抽取数据不放回(伯努利算法)
* 第一个参数:抽取数据后是否将数据放回 true--放回 false--丢弃
* 第二个参数:抽取的几率,范围在[0,1]之间,0--全不取;1--全取
* 第三个参数:抽取数据时随机算法的种子 如果不传递第三个参数,那么使用的是当前系统时间
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
//第一个参数表示,抽取数据后是否将数据放回 true--放回 false--丢弃
val sampleRdd: RDD[Int] = rdd.sample(false, 0.4, 1)
println(sampleRdd.collect().mkString(","))
/**
* 抽取数据放回(泊松算法)
* 第一个参数:抽取的数据是否放回,true:放回;false:不放回
* 第二个参数:重复数据的几率,范围大于等于 0.表示每一个元素被期望抽取到的次数
* 第三个参数:随机数种子
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
//第一个参数表示,抽取数据后是否将数据放回 true--放回 false--丢弃
val sampleRdd: RDD[Int] = rdd.sample(true, 2)
println(sampleRdd.collect().mkString(","))
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
将数据集中重复的数据去重
/**
* distinct 去重
*
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 1, 2, 3, 4))
val disRdd: RDD[Int] = rdd.distinct()
disRdd.collect().foreach(println)
def coalesce(numPartitions: Int, shuffle: Boolean = false,partitionCoalescer: Option[PartitionCoalescer] = Option.empty) (implicit ord: Ordering[T] = null) : RDD[T]
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本
/**
* coalesce 缩减分区
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 4)
rdd.saveAsTextFile("out1")
val newRdd: RDD[Int] = rdd.coalesce(2)
newRdd.saveAsTextFile("output")
防止出现数据倾斜问题
/**
* coalesce 缩减分区
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4,5,6), 3)
// coalesce 方法默认情况下不会将分区的数据打乱重新组合
//这种情况下缩减分区可以能导致数据不均衡,出现数据倾斜
//如果想要让数据均衡,可以进行shuffle处理,传第二个参数true
//val newRdd: RDD[Int] = rdd.coalesce(2)
val newRdd: RDD[Int] = rdd.coalesce(2,true)
newRdd.saveAsTextFile("output")
需要使用shuffle操作
与repartition作用相同
/**
* coalesce 扩大分区
* 与repartition作用相同
* repartition底层使用 coalesce方法并且shuffle默认设为true
*/
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6), 2)
//coalesce 可以扩大分区,但是如果不进行shuffle操作,是没有意义,不起作用
//所以如果想要扩大分区的效果,需要使用shuffle操作
val newRdd: RDD[Int] = rdd.coalesce(3,true)
newRdd.saveAsTextFile("output")
def sortBy[K](f: (T) => K, ascending: Boolean = true,numPartitions: Int = this.partitions.length) (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一致。中间存在 shuffle 的过程
/**
* sortBy
* 中间存在 shuffle 的过程
* 可以根据指定的规则对数据源中的数据进行排序,默认升序
* 第二个参数可以改变排序方式
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("1", 1), ("11", 2), ("2", 3)), 2)
val newRdd: RDD[(String, Int)] = rdd.sortBy(t => t._1.toInt)
newRdd.collect().foreach(println)
//改变排序方式
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("1", 1), ("11", 2), ("2", 3)), 2)
val newRdd: RDD[(String, Int)] = rdd.sortBy(t => t._1.toInt,false)
newRdd.collect().foreach(println)
def intersection(other: RDD[T]): RDD[T]
对源 RDD 和参数 RDD 求交集后返回一个新的 RDD
/**
* 双value类型
* intersection
* 交集
*
*/
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))
val intRdd: RDD[Int] = rdd1.intersection(rdd2)
intRdd.collect().foreach(println)
def union(other: RDD[T]): RDD[T]
对源 RDD 和参数 RDD 求并集后返回一个新的 RDD
/**
* union
* 并集
*/
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))
val rdd3: RDD[Int] = rdd1.union(rdd2)
println(rdd3.collect().mkString(","))
def subtract(other: RDD[T]): RDD[T]
以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求差集
/**
* subtract
* 差集
*/
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))
val rdd: RDD[Int] = rdd1.subtract(rdd2)
println(rdd.collect().mkString(","))//1,2
val rdd3: RDD[Int] = rdd2.subtract(rdd1)
println(rdd3.collect().mkString(","))//5,6
def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素
/**
* 拉链
* 可以数据类型不一致
* 两个数据源要求分区数量保持一致
* 每一个分区的元素数量要保持一样
*/
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
val rdd2: RDD[Int] = sc.makeRDD(List(3,4,5,6))
val rdd: RDD[(Int, Int)] = rdd1.zip(rdd2)
println(rdd.collect().mkString(","))
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitione
/**
*partitionBy
* 根据指定的分区规则对数据进行重分区
* 使用默认HashPartitioner分区器分区
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
val mapRdd: RDD[(Int, Int)] = rdd.map((_, 1))
//RDD=>PairRDDFunctions
//隐式转换
mapRdd.partitionBy(new HashPartitioner(2)).saveAsTextFile("output")
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
可以将数据按照相同的 Key 对 Value 进行聚合
/**
* reduceByKey
* 相同的key的数据进行value数据操作
* scala语言中的聚合一般是两两聚合,
* spark基于scala开发,所以spark聚合也是两两聚合
* reduceByKey中如果数据只有一个,是不会参与运算的
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("b", 4)))
val reRdd: RDD[(String, Int)] = rdd.reduceByKey((x, y) => {
println(s"x=${x},y=${y}")
x + y
})
reRdd.collect().foreach(println)
def groupByKey(): RDD[(K, Iterable[V])]
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
将数据源的数据根据 key 对 value 进行分组
/**
* groupByKey
* 将数据源中的数据,相同key的数据分在一个组中,形成一个对偶元组
* 元组中第一个元素就是key
* 元组中第二个元素就是相同key的value集合
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("b", 4)))
val groupRdd: RDD[(String, Iterable[Int])] = rdd.groupByKey()
groupRdd.collect().foreach(println)
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,combOp: (U, U) => U): RDD[(K, U)]
将数据根据不同的规则进行分区内计算和分区间计算
//key---value类型
/**
* aggregateByKey
* 存在函数的柯里化 有两个参数列表
* 第一个参数列表需要传递一个参数,表示初始值
* 主要用于当碰见第一个key的时候和value进行分区计算
* 第二个参数列表需要传递2个参数
* 第一个参数表示分区内计算规则
* 第二个参数表示分区间计算规则
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)),2)
// 取出每个分区内相同 key 的最大值然后分区间相加
rdd.aggregateByKey(0)(
(x,y) =>math.max(x,y),
(x,y) =>x+y
).collect().foreach(println)
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey
/**
* foldByKey
* 当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(List(
("a", 1), ("a", 2),("a", 3),
("b", 4), ("b",5),("a",6)
),2)
val foldRdd: RDD[(String, Int)] = rdd.foldByKey(0)(_ + _)
foldRdd.collect().foreach(println)
def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C): RDD[(K, C)]
最通用的对 key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于aggregate(),combineByKey()允许用户返回值的类型与输入不一致。
/**
* combineByKey
* 需要三个参数
* 第一个参数:将相同key的第一个数据进行结构转换
* 第二个参数:分区内的计算规则
* 第三个参数:分区间的计算规则
*
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(List(
("a", 1), ("a", 2),("b", 3),
("b", 4), ("b",5),("a",6)
),2)
//获取相同key的平均值 =>(a,3)(b,4)
val newRdd: 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)
}
)
val resutRdd: RDD[(String, Int)] = newRdd.mapValues {
case (num, cnt) => {
num / cnt
}
}
resutRdd.collect().foreach(println)
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length) : RDD[(K, V)]
在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 key 进行排序的
/**
* sortByKey
* true:字典升序
* false:字典降序
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
val sortRdd: RDD[(String, Int)] = rdd.sortByKey(true)
sortRdd.collect().foreach(println)
println("=================")
val sortRdd1: RDD[(String, Int)] = rdd.sortByKey(false)
sortRdd1.collect().foreach(println)
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素连接在一起的(K,(V,W))的 RDD
/**
* join
* 两个不同数据源的数据,相同key的value会连接在一起,形成元组
* 如果两个数据源中key没有匹配上,那么数据不会出现在结果中
* 如果两个数据源中key有多个相同的,会依次匹配,可能会出现笛卡尔积,数据量会几何增长,会导致性能降低
*/
val rdd1: RDD[(String, Int)] = sc.makeRDD(List(
("a", 1), ("b", 2),("c", 3)))
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(
("a", 4), ("b", 5), ("c", 6)))
val joinRdd: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
joinRdd.collect().foreach(println)
def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]
类似于 SQL 语句的左外连接
/**
* leftOuterJoin
*
*/
val rdd1: RDD[(String, Int)] = sc.makeRDD(List(
("a", 1), ("b", 2),("c", 3)))
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(
("a", 4), ("b", 5), //("c", 6)
))
val leftRdd: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)
leftRdd.collect().foreach(println)
右外连接
/**
* rightOuterJoin
*/
val rdd1: RDD[(String, Int)] = sc.makeRDD(List(
("a", 1), ("b", 2),//("c", 3)
))
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(
("a", 4), ("b", 5), ("c", 6)
))
val rightRdd: RDD[(String, (Option[Int], Int))] = rdd1.rightOuterJoin(rdd2)
rightRdd.collect().foreach(println)
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD
/**
* cogroup:connect+group
* 分组连接
* 最多可以连接三个
*/
val rdd1: RDD[(String, Int)] = sc.makeRDD(List(
("a", 1), ("b", 2),//("c", 3)
))
val rdd2: RDD[(String, Int)] = sc.makeRDD(List(
("a", 4), ("b", 5), ("c", 6)
))
val coRdd: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
coRdd.collect().foreach(println)
def reduce(f: (T, T) => T): T
聚集 RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据
/**
* reduce
* 数据源中的数据两两聚合
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val i: Int = rdd.reduce(_ + _)
println(i)
def collect(): Array[T]
在驱动程序中,以数组 Array 的形式返回数据集的所有元素
/**
* 底层代码调用的是环境对象runJob方法
* 底层代码中会创建ActiveJob,并提交执行
*
* collect
* 方法会将不同分区的数据按照分区顺序采集到Driver端内存中,形成数据
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val ints: Array[Int] = rdd.collect()
println(ints.mkString(","))
def count(): Long
返回 RDD 中元素的个数
/**
* count
* 数据源中的数据个数
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val l: Long = rdd.count()
println(l)
def first(): T
返回 RDD 中的第一个元素
/**
* first
* 取数据源中第一个数据
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val i: Int = rdd.first()
println(i)
def take(num: Int): Array[T]
返回一个由 RDD 的前 n 个元素组成的数组
/**
* take
* 取数据源中n个数据
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val ints: Array[Int] = rdd.take(3)
println(ints.mkString(","))
def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]
返回该 RDD 排序后的前 n 个元素组成的数组
/**
* takeOrdered
* 默认升序
* 数据排序后取n个数据
* 可以传第二个参数使得降序排序
*/
val rdd: RDD[Int] = sc.makeRDD(List(4,2,3,1))
val ints: Array[Int] = rdd.takeOrdered(3)
println(ints.mkString(","))
val ints1: Array[Int] = rdd.takeOrdered(3)(Ordering.Int.reverse)
println(ints1.mkString(","))
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
/**
* aggregate:初始值会参与分区计算,并且参与分区间的计算
* 10+13+17=40
* aggregateByKey:初始值只会参与分区内计算
*/
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val result: Int = rdd.aggregate(10)(_ + _, _ + _)
println(result)
def fold(zeroValue: T)(op: (T, T) => T): T
折叠操作,aggregate 的简化版操作
/**
* fold
* 分区内和分区间计算规则是相同的
* 10+13+17=40
*/
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val result: Int = rdd.fold(10)(_ + _)
println(result)
def countByKey(): Map[K, Long]
统计每种 key 的个数
/**
* countByKey
* 统计每种 key 的个数
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("a", 1), ("a", 5)
))
val stringToLong: collection.Map[String, Long] = rdd.countByKey()
println(stringToLong)
统计每种value的个数
/**
* countByValue
*
*/
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val intToLong: collection.Map[Int, Long] = rdd.countByValue()
println(intToLong)
def saveAsTextFile(path: String): Unit
def saveAsObjectFile(path: String): Unit
def saveAsSequenceFile( path: String,codec: Option[Class[_ <: CompressionCodec]] = None): Unit
将数据保存到不同格式的文件中
/**
* saveAsTextFile
* saveAsObjectFile
* saveAsSequenceFile
*/
val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("a", 1), ("a", 5)
),3)
rdd.saveAsTextFile("output")
rdd.saveAsObjectFile("output1")
//要求数据格式必须是k-v类型
rdd.saveAsSequenceFile("output2")
def foreach(f: T => Unit): Unit = withScope {
val cleanF = sc.clean(f)
sc.runJob(this, (iter: Iterator[T]) => iter.foreach(cleanF))
}
分布式遍历 RDD 中的每一个元素,调用指定函数
/**
* foreach
*
*/
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
//先采集
//foreach是Driver端内集合的循环遍历方法
rdd.collect().foreach(println)
println("==========")
//foreach是Executor端数据打印
rdd.foreach(println)