Transformation转换算子
RDD整体上可以分为三种类型:
- Value类型
- 双Value类型
- Key-Value类型
1.Value类型
-
Map算子
- 进去一行,出来一行
- 将一个RDD中的每个数据项,通过map中的函数映射变为一个新的元素。
- 有多少个输入分区,就会有多少个输出的分区
- 有可能改变数据项的数据类型
-
MapMartitions算子
- 一次处理一个分区
- 工作中,内存足够的情况下,优先使用
- 是否保留上有RDD的分区信息,默认为false
- 把每一个分区中的元素放到迭代器中,所以在mappartition里面做的第一层操作时针对迭代器
-
Map和MapMartitions的区别:
- map一次处理一个,mappartition一次处理一个分区
- map分区中的每个元素,计算完之后释放,mapPartition处理完一个分区之后才会释放资源
- 内存足够,优先选择
-
MapPartitionsWithIndex算子
- 函数签名:f:
(Int, Iterator[T]) => Iterator[U], // Int表示分区编号 - 第一个参数是分区号,第二个参数是迭代器
- 使用:val indexRdd =
rdd.mapPartitionsWithIndex( (index,items)=>{items.map( (index,_) )} ) - 一般是需要对迭代器进行操作的
- 函数签名:f:
-
FlatMap算子
- 函数签名:def
flatMap【U: ClassTag】(f: T => TraversableOnce[U]): RDD[U] - 功能说明:
- 进来一个,出去多个
- 需要对迭代器进行操作
- 演示:listRDD.flatMap(list=>list).collect.foreach(println)
- 函数签名:def
-
glom算子(不常用算子)
- 功能说明:分区转换数组
- 函数签名:def
glom(): RDD[Array[T]] - 没有参数,谁调用他,返回数组
- 将每一个分区变成一个数组,并放置在新的RDD中,数组中的元素和原来分区中元素类型保持一致
- 用的不多,因为每个分区中的数据很多,容易oom
- 演示:val maxRdd: RDD[Int]
= rdd.glom().map(_.max) - 遍历元素最大值:用的最多的就是map
- println(maxRdd.collect().sum)
-
GroupBy分组
-
函数签名:
f: T => K, numPartitions: Int)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
-
ReducebuKey做了一定的优化,相比GroupBy用的更多
-
groupby的结果就是(0,CompactBuffer(2,4))
-
使用的方式:rdd.groupBy(_.substring(0,1))
-
groupby会存在shuffle过程
-
将不同的分区数据进行打乱重组
-
一定会落盘
-
案例:
-
def main(args: Array[String]): Unit = { //1.创建SparkConf并设置App名称 val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]") //2.创建SparkContext,该对象是提交Spark App的入口 val sc = new SparkContext(conf) //3具体业务逻辑 // 3.1 创建一个RDD val strList: List[String] = List("Hello Scala", "Hello Spark", "Hello World") val rdd = sc.makeRDD(strList) // 3.2 将字符串拆分成一个一个的单词 val wordRdd: RDD[String] = rdd.flatMap(str=>str.split(" ")) // 3.3 将单词结果进行转换:word=>(word,1) val wordToOneRdd: RDD[(String, Int)] = wordRdd.map(word=>(word, 1)) // 3.4 将转换结构后的数据分组 val groupRdd: RDD[(String, Iterable[(String, Int)])] = wordToOneRdd.groupBy(t=>t._1) // 3.5 将分组后的数据进行结构的转换 val wordToSum: RDD[(String, Int)] = groupRdd.map { case (word, list) => { (word, list.size) } } wordToSum.collect().foreach(println) sc.stop() }
-
-
Filter过滤算子
- 函数签名: def filter(f: T =>
Boolean): RDD[T] - 使用:rdd.filter(_%2==0)
- 函数签名: def filter(f: T =>
-
Sample()采样
-
函数签名:
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
-
第一个参数输入false,就会选择伯努利算法,数据不放回(扔硬币,范围在0-1之间)如果大于第二个参数不打印,不大于全打印,第三个参数是会根据种子生成伪随机数;
-
第一个参数是true,选择泊松算法数据放回。第二个参数是希望出现次数的概率。
-
有什么用?在开发时候,不是很清楚key的分布,抽取一部分的数据进行分析。
-
-
Distinct去重算子
- 去重的操作不适用Distinct有那些方式?
- set,groupBy,开窗
- 使用:rdd.distinct
- 这种是分布式的方式去重,比HashSet集合的方式不容易oom
- 去重可以修改分区个数
- 去重思路:
-
coalesce合并分区算子
-
配置shuffle
- rdd.coalesce(2,true)
-
不配置shuffle
-
功能:缩减分区
-
val rdd = sc.makeRDD(Array(1,2,3,4),4) val coalesceRdd = rdd.coalesce(2) //打印查看对应分区的数据 val indexRdd = coalesceRdd.mapPartitionsWithIndex( (index,datas)=>{ //打印每个分区数据,并带上分区号 datas.foreach(data=>{ println(index+"=>"+data) }) //返回分区的数据 datas } )
-
-
-
repartition重新分区
- 内部执行的其实是coalesce操作,参数shuffle的默认是true/无论是将分区数多的rdd转换为分区数少的rdd,还是将分区少的rdd转换为分区数多的rdd,repartition操作都可以完成,因为无论如何都会经过shuffle过程。
-
coalesce和partition的区别
- coalesce一般是缩减分区,如果扩大分区,不适用shuffle是没有意义的。
-
sortby排序
- rdd.sortBy(num)
-
pipe
- 函数签名: def pipe(command: String):
RDD[String] - 针对每个分区都调用一次shell脚本
- 函数签名: def pipe(command: String):
2.双value类型交互
- itersection交集
- 函数签名:def intersection(other:
RDD[T]): RDD[T] - val rdd1 = sc.makeRDD(1 to 4)
- val rdd2 = sc.makeRDD(4 to 8)
- rdd1.intersection(rdd2).collection.foreach(println)
- 函数签名:def intersection(other:
- union
- 函数签名:def union(other: RDD[T]):
RDD[T] - rdd1.union(rdd2).collection.foreach(println)
- 函数签名:def union(other: RDD[T]):
- substract
- 函数签名:def subtract(other: RDD[T]):
RDD[T] - rdd1.substract(rdd2).collection.foreach(println)
- 函数签名:def subtract(other: RDD[T]):
- zip
- 函数签名:def zip[U: ClassTag](other:
RDD[U]): RDD[(T, U)] - 必须分区数相同,并且几个元素的个数意义对应
- 函数签名:def zip[U: ClassTag](other:
3.Key-Value
-
partitionBy按照k重新分区
-
函数签名:def partitionBy(partitioner:
Partitioner): RDD[(K, V)] -
如果原有的分区数和新的一致的话,就不进行分区,否则就会产生shuffle过程
-
传入的kv类型
-
自定义分区
// 自定义分区 class MyPartitioner(num: Int) extends Partitioner { // 设置的分区数 override def numPartitions: Int = num // 具体分区逻辑 override def getPartition(key: Any): Int = { if (key.isInstanceOf[Int]) { val keyInt: Int = key.asInstanceOf[Int] if (keyInt % 2 == 0) 0 else 1 }else{ 0 } } }
//4 打印查看对应分区数据 val indexRdd = rdd3.mapPartitionsWithIndex( (index, datas) => datas.map((index,_)) ) indexRdd.collect() //5.关闭连接 sc.stop()
-
-
reduceByKey()按照K聚合V
- def reduceByKey(func: (V, V)
=> V): RDD[(K, V)] - def reduceByKey(func: (V, V)
=> V, numPartitions: Int): RDD[(K, V)] - 功能说明:该操作可以让rdd中的元素按照相同的kv进行聚合。
- 和partitionby相比存在分区内和分区间的操作。会在分区内先进性一次combiner
- 和groupby的效果差不多,但是比groupby方便
- def reduceByKey(func: (V, V)
-
groupByKey()按照K重新分组
-
函数签名:def groupByKey(): RDD[(K,
Iterable[V])] -
groupbykey只对每个key进行操作,但是只生成一个seq,并不进行聚合。
-
有shuflle
val rdd = sc.makeRDD(List(("a",1),("b",2),("a",1))) //将相同的key都聚合到一个seq中 val group = rdd.groupBykey().collect()>foreach(println) group.map(t=>(t._1,t._2.sum)).collect().foreach(println)
-
-
reduceByKey和groupByKey区别
- reducebykey:按照key进行聚合,在shuffle之气那有combine操作,返回的结果是rdd[k,v]
- groupbykey:按照key进行分组,直接进行shuffle
- 开发指导:在不影响业务逻辑的前提下,优先使用reducebykey。求和操作不影响业务,求平均值影响业务逻辑
-
aggregatebykey按照k处理分区内和分区间的逻辑
-
//3.1 创建第一个RDD val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2) //3.2 取出每个分区相同key对应值的最大值,然后相加 rdd.aggregateByKey(0)(math.max(_, _), _ + _).collect().foreach(println)
-
-
foladbykey分区内和分区间相同的aggregateByKey
-
中间有shuffle
-
val list: List[(String, Int)] = List(("a",1),("a",1),("a",1),("b",1),("b",1),("b",1),("b",1),("a",1)) val rdd = sc.makeRDD(list,2) //3.2 求wordcount //rdd.aggregateByKey(0)(_+_,_+_).collect().foreach(println) rdd.foldByKey(0)(_+_).collect().foreach(println)
-
-
combineByKey()转换结构后分区内和分区间操作
- def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
//3.1 创建第一个RDD val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)) val input: RDD[(String, Int)] = sc.makeRDD(list, 2) //3.2 将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组 val combineRdd: RDD[(String, (Int, Int))] = input.combineByKey( (_, 1), (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1), (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2) ) //3.3 打印合并后的结果 combineRdd.collect().foreach(println) //3.4 计算平均值 combineRdd.map { case (key, value) => { (key, value._1 / value._2.toDouble) } }.collect().foreach(println)
-
sortByKey()按照K进行排序
-
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
-
//3具体业务逻辑 //3.1 创建第一个RDD val rdd: RDD[(Int, String)] = sc.makeRDD(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd"))) //3.2 按照key的正序(默认顺序) rdd.sortByKey(true).collect().foreach(println) //3.3 按照key的倒序 rdd.sortByKey(false).collect().foreach(println)
-
-
mapValues()只对V进行操作
-
//3具体业务逻辑 //3.1 创建第一个RDD val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (1, "d"), (2, "b"), (3, "c"))) //3.2 对value添加字符串"|||" rdd.mapValues(_ + "|||").collect().foreach(println)
-
-
join()连接
-
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
-
//3具体业务逻辑 //3.1 创建第一个RDD val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c"))) //3.2 创建第二个pairRDD val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6))) //3.3 join操作并打印结果 rdd.join(rdd1).collect().foreach(println)
-
-
cogroup()类似全连接,但是在同一个RDD中对key聚合
-
def
cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))] -
//3具体业务逻辑 //3.1 创建第一个RDD val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"a"),(2,"b"),(3,"c"))) //3.2 创建第二个RDD val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1,4),(2,5),(3,6))) //3.3 cogroup两个RDD并打印结果 rdd.cogroup(rdd1).collect().foreach(println)
-
able[W]))]
2. ```scala
//3具体业务逻辑
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"a"),(2,"b"),(3,"c")))
//3.2 创建第二个RDD
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1,4),(2,5),(3,6)))
//3.3 cogroup两个RDD并打印结果
rdd.cogroup(rdd1).collect().foreach(println)
```