RDD的Transformation(转换算子)
RDD整体上分为Value类型、双Value类型和Key-Value类型
1 value类型
创建包名:com.xiao_after.value
1.1 map()映射
1)函数签名:
def map[U: ClassTag](f: T => U): RDD[U]
2)功能说明:参数 f 是一个函数,它可以接收一个参数。当某个RDD执行map方法时,
会遍历该RDD中的每一个数据项,并依次应用 f 函数,从而产生一个新的RDD。即,这个
新的RDD中的每一个元素都是原来RDD中每一个元素依次应用 f 函数而得到的。
3)需求说明:创建一个1-4数组的RDD,两个分区,将所有元素*2形成新的RDD
4)具体实现
package com.xiao_after.value
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
/**
* @author xiaohu
* @create 2020-09-24 21:06
*/
object value01_map {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个rdd
val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)
//3.2 调用map方法,每个元素乘以2
val rdd1: RDD[Int] = rdd.map(_ * 2)
//3.3 遍历打印输出rdd1
rdd1.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)输出结果
2
4
6
8
1.2 mapPartitions()以分区为单位执行Map
1)函数签名:
def mapPartitions[U: ClassTag](f: Iterator[T] => Iterator[U]
2)功能说明:Map是依次处理一个元素,而mapPartitions一次处理一个分区数据。
3)需求说明:创建一个RDD,4个元素,2个分区,使每个元素组成新的RDD。
4)具体实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 21:16
*/
object value02_mapPartitions {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
//3.1 创建一个rdd
val rdd: RDD[Int] = sc.makeRDD(1 to 4,2)
//3.2 调用mapPartitions方法,每个元素乘以2
val rdd1: RDD[Int] = rdd.mapPartitions(_.map(_*2))
//3.3 遍历打印rdd1中的数据
rdd1.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)输出结果
2
4
6
8
1.3 map()和mapPartitions()区别
1)map():每次处理一条数据;
2)mapPartitions():每次处理一个分区的数据,这个分区的数据处理完后,元RDD分区
中的数据才能释放,可能导致OOM;
3)开发经验:当内存空间较大的时候,建议使用 mapPartitions(),以提高处理效率。
1.4 mapPartitionsWithIndex()带分区号
1)函数签名
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
2)功能说明:类似于mapPartitions,比mapPartitions多一个整数参数表示分区号
3)需求说明:创建一个RDD,使每个元素跟所在分区号形成一个元组,组成一个新的
RDD
4)具体实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 21:07
*/
object value03_mapPartitionsWithIndex {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)
//3.2 使每个元素跟所在分区号形成一个元组,组成一个新的RDD
val rdd1: RDD[(Int, Int)] = rdd.mapPartitionsWithIndex((index, items) => {
items.map((index, _))
})
//3.3 遍历打印rdd1的数据
rdd1.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)输出结果
(0,1)
(0,2)
(1,3)
(1,4)
1.5 flatMap()扁平化
1)函数签名
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
2)功能说明
与map操作类似,将RDD中的每一个元素通过应用f函数依次转换为新的元素,并封装到
RDD中。区别:在flatMap操作中,f函数的返回值是一个集合,并且会将每一个该集合中
的元素拆分出来放到新的RDD中;
3)需求说明:创建一个集合,集合里面存储的还是子集合,把所有子集合中数据取出放
入到一个大的集合中;
4)具体实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 22:55
*/
object value04_flatMap {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[List[Int]] = sc.makeRDD(List(List(1, 2), List(3, 4), List(5, 6), List(7)), 2)
//3.2 把所有子集合中数据取出放入到一个大的集合中
val rdd1: RDD[Int] = rdd.flatMap(list => list)
//3.3 遍历打印rdd1的数据
rdd1.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
1
2
3
4
5
6
7
1.6 glom()分区转换数组
1)函数签名
def glom(): RDD[Array[T]]
2)功能说明:
该操作将RDD中每一个分区变成一个数组,并放置在新的RDD中,数组中元素的类型与
原分区中元素类型一致
3)需求说明:创建一个2个分区的RDD,并将每个分区的数据放到一个数组,求出每个
分区的最大值
4)具体实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 22:56
*/
object value05_glom {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)
//3.2 求出每个分区的最大值
val rdd1: RDD[Int] = rdd.glom().map(_.max)
//3.3 求出所有分区的最大值的和 2 + 4
println(rdd1.collect().sum)
//4.关闭连接
sc.stop()
}
}
5)运行结果
6
1.7 groupBy()分组
1)函数签名:
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
2)功能说明:分组,按照传入函数的返回值进行分组。将相同的 key 对应的值放入一个
迭代器
3)需求说明:创建一个RDD,按照元素模以2的值进行分组
4)具体实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 22:56
*/
object value06_groupby {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)
//3.2 将每个分区的数据放到一个数组并收集到Driver端打印
rdd.groupBy(_ % 2).collect().foreach(println)
println("--------------------------")
//3.3 再创建一个RDD
val rdd1: RDD[String] = sc.makeRDD(List("hello", "hive", "hadoop", "spark", "scala"))
//3.4 按照首字母第一个单词相同分组
rdd1.groupBy(str => str.substring(0, 1)).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(0,CompactBuffer(2, 4))
(1,CompactBuffer(1, 3))
--------------------------
(s,CompactBuffer(spark, scala))
(h,CompactBuffer(hello, hive, hadoop))
注:groupBy会存在shuffle过程;
shuffle:将不同的分区数据进行打乱重组的过程;
shuffle一定会落盘。可以在local模式下执行程序,通过4040看效果。
1.8 GroupBy之WordCount
1)需求说明:创建一个RDD,使用 GroupBy 实现 WordCount 功能
2)具体实现:
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 23:37
*/
object value07_groupby_wordcount {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = 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 rdd1: RDD[String] = rdd.flatMap(_.split(" "))
//3.3 将单词结果进行转换,(word) => (word,1)
val rdd2: RDD[(String, Int)] = rdd1.map((_, 1))
//3.4 将转换结构后的数据分组
val rdd3: RDD[(String, Iterable[(String, Int)])] = rdd2.groupBy(_._1)
//3.5 将分组后的数据进行结构的转换
val rdd4: RDD[(String, Int)] = rdd3.map {
case (word, list) =>
(word, list.size)
}
rdd4
//3.6 遍历打印rdd4
rdd4.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
3)运行结果
(Hello,3)
(World,1)
(Scala,1)
(Spark,1)
1.9 filter()过滤
1)函数签名
def filter(f: T => Boolean): RDD[T]
2)功能说明
接收一个返回值为布尔类型的函数作为参数。当某个RDD调用filter方法时,会对该RDD中
每一个元素应用f函数,如果返回值类型为true,则该元素会被添加到新的RDD中。
3)需求说明:创建一个RDD,过滤出对2取余等于0的数据
4)代码实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 23:37
*/
object value08_filter {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1,2,3,4,5),2)
//3.2 过滤出符合条件的数据
val rdd1: RDD[Int] = rdd.filter(_ % 2 == 0)
//3.3 遍历打印rdd1的数据
rdd1.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
2
4
1.10 sample()采样
1)函数签名
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
// withReplacement: true为有放回的抽样,false为无放回的抽样;
// fraction表示:以指定的随机种子随机抽样出数量为fraction的数据;
// seed表示:指定随机数生成器种子。
2)功能说明
从大量的数据中采样
3)需求说明:创建一个RDD(1-10),从中选择放回和不放回抽样
4)代码实现
package com.xiao_after.partition
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 23:37
*/
object value09_sample {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6))
//3.2
// 抽取数据不放回(伯努利算法)
// 伯努利算法:又叫0、1分布。例如扔硬币,要么正面,要么反面。
// 具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不要
// 第一个参数:抽取的数据是否放回,false:不放回
// 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取;
// 第三个参数:随机数种子
val rdd1: RDD[Int] = rdd.sample(false, 0.5)
rdd1.collect().foreach(println)
println("-------------------------")
//3.3
// 抽取数据放回(泊松算法)
// 第一个参数:抽取的数据是否放回,true:放回;false:不放回
// 第二个参数:重复数据的几率,范围大于等于0.表示每一个元素被期望抽取到的次数
// 第三个参数:随机数种子
val rdd2: RDD[Int] = rdd.sample(true, 2)
rdd2.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
1
3
4
-------------------------
1
2
2
2
2
3
4
4
5
5
5
5
6
6
1.11 distinct()去重
1)函数签名:
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
2)功能说明:对内部的元素去重,并将去重后的元素放到新的 RDD 中
3)具体实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 23:38
*/
object value10_distinct {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,4,3,2,1))
//3.2 对rdd执行去重操作并打印修改后的数据
rdd.distinct().collect().foreach(println)
println("-------------------------------")
//3.3 对rdd采用多个Task去重,提高并发度
rdd.distinct(2).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
4)运行结果
1
2
3
4
5
-------------------------------
4
2
1
3
5
1.12 coalesce()合并分区
Coalesce算子包括:配置执行Shuffle和配置不执行Shuffle两种方式。
1、不执行Shuffle方式
1)函数签名
def coalesce(numPartitions: Int, shuffle: Boolean = false, //默认false不执行shuffle
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null) : RDD[T]
2)功能说明:缩减分区数,用于大数据集过滤后,提高小数据集的执行效率
3)需求:4个分区合并为2个分区
4)代码实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 23:38
*/
object value11_coalesce {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1,2,3,4),4)
//3.2 执行缩减分区操作
val rdd1: RDD[Int] = rdd.coalesce(2)
//3.3 查看每个数据的分区编号
val rdd2: RDD[(Int, Int)] = rdd1.mapPartitionsWithIndex {
case (index, items) =>
items.map((index, _))
}
rdd2
//3.4 遍历打印rdd2的数据
rdd2.collect()foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(0,1)
(0,2)
(1,3)
(1,4)
2、执行 Shuffle 方式
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)
//3.2 执行缩减分区操作
val rdd1: RDD[Int] = rdd.coalesce(2, true)
运行结果
(0,1)
(0,4)
(0,5)
(1,2)
(1,3)
(1,6)
1.13 repartition()重新分区(执行Shuffle)
1)函数签名
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
2)功能说明
该操作内部其实执行的是coalesce操作,参数shuffle的默认值为true。无论是将分区数多
的RDD转换为分区数少的RDD,还是将分区数少的RDD转换为分区数多的RDD,
repartition操作都可以完成,因为无论如何都会经shuffle过程。
3)需求说明:创建一个4个分区的RDD,对其重新分区。
4)代码实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 23:38
*/
object value12_repartition {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)
//3.2 增加分区,不开启shuffle
val rdd1: RDD[Int] = rdd.coalesce(4)
//3.3 增加分区,开启shuffle
val rdd2: RDD[Int] = rdd.coalesce(4, true)
//3.4 重新分区
val rdd3: RDD[Int] = rdd.repartition(4)
//3.5 将修改后的rdd保存在文件夹下
rdd1.saveAsTextFile("D:\\MyselfPractice\\HdfsClientDemo\\SparkCoreTest\\output6")
rdd2.saveAsTextFile("D:\\MyselfPractice\\HdfsClientDemo\\SparkCoreTest\\output7")
rdd3.saveAsTextFile("D:\\MyselfPractice\\HdfsClientDemo\\SparkCoreTest\\output8")
//4.关闭连接
sc.stop()
}
}
5)运行结果
output6下面有3个分区,说明这种 coalesce 不开启shuffle来增加分区的方式没有意义
output7下面有4个分区,因为 coalesce 开启了shuffle过程
output8下面有4个分区,因为 repartition 默认开启shuffle 过程
1.14 coalesce和repartition区别
1)coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean =
false/true决定。
2)repartition实际上是调用的coalesce,进行shuffle。源码如下:
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
coalesce(numPartitions, shuffle = true)
}
3)coalesce一般为缩减分区,如果扩大分区,不使用shuffle是没有意义的,repartition扩
大分区执行shuffle。
1.15 sortBy()排序
1)函数签名
def sortBy[K]( f: (T) => K,
ascending: Boolean = true, // 默认为正序排列
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
2)功能说明
该操作用于排序数据。在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理
的结果进行排序,默认为正序排列。排序后新产生的RDD的分区数与原RDD的分区数一
致。
3)需求说明:创建一个RDD,按照数字大小分别实现正序和倒序排序
4)代码实现
package com.xiao_after.value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-24 23:39
*/
object value13_sortBy {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(List(1, 6, 2, 5, 4, 3))
//3.2 默认是升序排
val rdd1: RDD[Int] = rdd.sortBy(num => num)
//3.3 遍历打印rdd1中的数据
rdd1.collect().foreach(println)
println("-------------------------")
//3.4 配置为倒序排
val rdd2: RDD[Int] = rdd.sortBy(num => num, false)
//3.5 遍历打印rdd2中的数据
rdd2.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
1
2
3
4
5
6
-------------------------
6
5
4
3
2
1
1.16 pipe()调用脚本
1)函数签名:
def pipe(command: String): RDD[String]
2)功能说明
管道,针对每个分区,都调用一次shell脚本,返回输出的RDD。
注意:脚本需要放在Worker节点可以访问到的位置
3)需求说明:编写一个脚本,使用管道将脚本作用于RDD上。
(1)编写一个脚本,并增加执行权限
[xiao@hadoop102 spark-local]$ vim pipe.sh
#!/bin/bash
echo "Start"
while read LINE; do
echo ">>>"${LINE}
done
[xiao@hadoop102 spark-local]$ chmod 777 pipe.sh
(2)创建一个只有一个分区的RDD
[xiao@hadoop102 spark-local]$ bin/spark-shell
scala> val rdd = sc.makeRDD (List("hi","Hello","how","are","you"), 1)
(3)将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark-local/pipe.sh").collect()
res18: Array[String] = Array(Start, >>>hi, >>>Hello, >>>how, >>>are, >>>you)
(4)创建一个有两个分区的RDD
scala> val rdd = sc.makeRDD(List("hi","Hello","how","are","you"), 2)
(5)将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark-local/pipe.sh").collect()
res19: Array[String] = Array(Start, >>>hi, >>>Hello, Start, >>>how, >>>are, >>>you)
说明:一个分区调用一次脚本。
2 双 Value 类型交互
1)创建包名:com.xiao_after.doublevalue
2.1 intersection()交集
1)函数签名
def intersection(other: RDD[T]): RDD[T]
2)功能说明
对源RDD和参数RDD求交集后返回一个新的RDD
3)需求说明:创建两个RDD,求两个RDD的交集
4)代码实现
package com.xiao_after.doublevalue
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 0:53
*/
object DoubleValue01_intersection {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 4)
//3.2 创建第二个RDD
val rdd2: RDD[Int] = sc.makeRDD(4 to 8)
//3.3 求两个RDD的交集
val rdd3: RDD[Int] = rdd1.intersection(rdd2)
//3.4 遍历打印rdd3中的数据
rdd3.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
4
2.2 union()并集
1)函数签名:
def union(other: RDD[T]): RDD[T]
2)功能说明
对源RDD和参数RDD求并集后返回一个新的RDD
3)需求说明:创建两个RDD,求并集
4)代码实现
package com.xiao_after.doublevalue
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 0:53
*/
object DoubleValue02_union {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 4)
//3.2 创建第二个RDD
val rdd2: RDD[Int] = sc.makeRDD(4 to 8)
//3.3 求两个RDD的并集
val rdd3: RDD[Int] = rdd1.union(rdd2)
//3.4 遍历打印rdd3中的数据
rdd3.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
1
2
3
4
4
5
6
7
8
2.3 subtract()差集
1)函数签名:
def subtract(other: RDD[T]): RDD[T]
2)功能说明
计算差的一种函数,去除两个RDD中相同元素,不同的RDD将保留下来
3)需求说明:创建两个RDD,求第一个RDD与第二个RDD的差集
4)代码实现
package com.xiao_after.doublevalue
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 0:53
*/
object DoubleValue03_subtract {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 4)
//3.2 创建第二个RDD
val rdd2: RDD[Int] = sc.makeRDD(4 to 8)
//3.3 求两个RDD的差集
val rdd3: RDD[Int] = rdd1.subtract(rdd2)
//3.4 遍历打印rdd3中的数据
rdd3.collect().foreach(println)
println("---------------------------")
//3.5 求两个RDD的差集
val rdd4: RDD[Int] = rdd2.subtract(rdd1)
//3.6 遍历打印rdd3中的数据
rdd4.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
1
2
3
---------------------------
5
6
7
8
2.4 zip()拉链
1)函数签名:
def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
2)功能说明
该操作可以将两个RDD中的元素,以键值对的形式进行合并。其中,键值对中的Key为第
1个RDD中的元素,Value为第2个RDD中的元素。
将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数
量都相同,否则会抛出异常。
3)需求说明:创建两个RDD,并将两个RDD组合到一起形成一个(k,v)RDD
4)代码实现
package com.xiao_after.doublevalue
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 0:53
*/
object DoubleValue04_zip {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建第一个RDD
val rdd1: RDD[Int] = sc.makeRDD(Array(1,2,3),3)
//3.2 创建第二个RDD
val rdd2: RDD[String] = sc.makeRDD(Array("a","b","c"),3)
rdd1.zip(rdd2).collect().foreach(println)
println("----------------------------")
rdd2.zip(rdd1).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(1,a)
(2,b)
(3,c)
----------------------------
(a,1)
(b,2)
(c,3)
3 Key-Value类型
1)com.xiao_after.keyvalue
3.1.1 partitionBy()按照K重新分区
1)函数签名:
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
2)功能说明
将RDD[K,V]中的K按照指定Partitioner重新进行分区;
如果原有的RDD和新的RDD是一致的话就不进行分区,否则会产生Shuffle过程。
3)需求说明:创建一个3个分区的RDD,对其重新分区
3.1.2 自定义分区
1)HashPartitioner源码解读
class HashPartitioner(partitions: Int) extends Partitioner {
require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative.")
def numPartitions: Int = partitions
def getPartition(key: Any): Int = key match {
case null => 0
case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
}
override def equals(other: Any): Boolean = other match {
case h: HashPartitioner =>
h.numPartitions == numPartitions
case _ =>
false
}
override def hashCode: Int = numPartitions
}
2)自定义分区器
要实现自定义分区器,需要继承org.apache.spark.Partitioner类,并实现下面三个方
法。
(1)numPartitions: Int:返回创建出来的分区数。
(2)getPartition(key: Any): Int:返回给定键的分区编号(0到numPartitions-1)。
(3)equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark需要用这
个方法来检查你的分区器对象是否和其他分区器实例相同,这样Spark才可以判断两个
RDD的分区方式是否相同
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:04
*/
object KeyValue01_partitionBy {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3)
//3.2 按照hash值来分组
val rdd1: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(2))
//3.3 匹配组号并打印
rdd1.mapPartitionsWithIndex {
(indes, items) =>
items.map((indes, _))
}.collect().foreach(println)
println("---------------------------")
//3.4 按照自定义分区
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new myPartitioner(2))
//3.5 匹配组号并打印
rdd2.mapPartitionsWithIndex {
(index, items) =>
items.map((index, _))
}.collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
//自定义分区:按照对2取模来分
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
}
}
5)运行结果
(0,(2,bbb))
(1,(1,aaa))
(1,(3,ccc))
---------------------------
(0,(2,bbb))
(1,(1,aaa))
(1,(3,ccc))
3.2 reduceByKey()按照K聚合V
1)函数签名:
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
2)功能说明:该操作可以将RDD[K,V]中的元素按照相同的K对V进行聚合。其存在多种重
载形式,还可以设置新RDD的分区数。
3)需求说明:统计单词出现次数
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:05
*/
object KeyValue02_reduceByKey {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 5), ("b", 2)))
//3.2 执行reduceByKey并循环遍历
rdd.reduceByKey(_ + _).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(a,6)
(b,7)
3.3 groupByKey()按照K重新分组
1)函数签名:
def groupByKey(): RDD[(K, Iterable[V])]
2)功能说明
groupByKey对每个key进行操作,但只生成一个seq,并不进行聚合。
该操作可以指定分区器或者分区数(默认使用HashPartitioner)
3)需求说明:统计单词出现次数
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:05
*/
object KeyValue03_groupByKey {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 5), ("b", 2)))
//3.2 执行groupByKey
val rdd1: RDD[(String, Iterable[Int])] = rdd.groupByKey()
//3.3 计算相同key对应值的相加结果
rdd1.map(t => (t._1, t._2.sum)).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(a,6)
(b,7)
3.4 reduceByKey和groupByKey区别
1)reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回
结果是RDD[K,V]。
2)groupByKey:按照key进行分组,直接进行shuffle。
3)开发指导:在不影响业务逻辑的前提下,优先选用reduceByKey。求和操作不影响业
务逻辑,求平均值影响业务逻辑。
3.5 aggregateByKey()按照K处理分区内和分区间逻辑
1)函数签名
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
①zeroValue(初始值):给每一个分区中的每一种key一个初始值;
②seqOp(分区内):函数用于在每一个分区中用初始值逐步迭代value;
③combOp(分区间):函数用于合并每个分区中的结果。
2)需求分析:取出每个分区间相同的 key 对应值的最大值,然后相加
3)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:05
*/
object KeyValue04_aggregateByKey {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//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)
//4.关闭连接
sc.stop()
}
}
4)运行结果
(b,3)
(a,3)
(c,12)
3.6 foldByKey()分区内和分区间相同的aggregateByKey()
1)函数签名:
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
①参数zeroValue:是一个初始化值,它可以是任意类型;
②参数func:是一个函数,两个输入参数相同。
2)功能说明:aggregateByKey的简化操作,seqop和combop相同。即,分区内逻辑和
分区间逻辑相同。
3)需求说明:求wordCount
①创建一个RDD
②求wordCount
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:05
*/
object KeyValue05_foldByKey {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val list: List[(String, Int)] = List(("a", 1), ("a", 1), ("a", 1), ("b", 1), ("b", 1), ("b", 1), ("b", 1), ("a", 1))
val rdd: RDD[(String, Int)] = sc.makeRDD(list, 2)
//3.2 执行foldByKey
rdd.foldByKey(0)(_ + _).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(b,4)
(a,4)
3.7 combineByKey()转换结构后分区内和分区间操作
1)函数签名:
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
(1)createCombiner(转换数据的结构): combineByKey() 会遍历分区中的所有元
素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这
是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个
键对应的累加器的初始值
(2)mergeValue(分区内): 如果这是一个在处理当前分区之前已经遇到的键,它会使
用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并
(3)mergeCombiners(分区间): 由于每个分区都是独立处理的,因此对于同一个键
可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器,就需要使
用用户提供的mergeCombiners()方法将各个分区的结果进行合并。
2)功能说明
针对相同K,将V合并成一个集合。
3)需求说明:创建一个pairRDD,根据key计算每种key的均值。(先计算每个key出现
的次数以及可以对应值的总和,再相除得到结果)
4)需求分析:针对一个pairRDD,计算每种key对应值的和,再根据key计算每种key的
均值。
5)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:05
*/
object KeyValue06_combineByKey {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
val rdd: RDD[(String, Int)] = sc.makeRDD(list, 2)
//3.2
rdd.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)
).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
6)运行结果
(b,4)
(a,4)
3.8 reduceByKey、foldByKey、aggregateByKey、combineByKey
reduceByKey:第一个初始值不变
foldByKey:分区内和分区间规则一致
aggregateByKey:第一个初始值和分区内处理规则一致
combineByKey:把第一个值变成特定的结构
3.9 sortByKey()按照K进行排序
1)函数签名:
def sortByKey(
ascending: Boolean = true, // 默认,升序
numPartitions: Int = self.partitions.length) : RDD[(K, V)]
2)功能说明
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)
的RDD
3)需求说明:创建一个pairRDD,按照key的正序和倒序进行排序
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:06
*/
object KeyValue07_sortByKey {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
//3.2 默认正序
rdd.sortByKey().collect().foreach(println)
println("-----------------------")
//3.3 设置反序
rdd.sortByKey(false).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(1,dd)
(2,bb)
(3,aa)
(6,cc)
-----------------------
(6,cc)
(3,aa)
(2,bb)
(1,dd)
3.10 mapValues()只对V进行操作
1)函数签名:
def mapValues[U](f: V => U): RDD[(K, U)]
2)功能说明:针对于(K,V)形式的类型只对V进行操作
3)需求说明:创建一个pairRDD,并将value添加字符串"$"
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:06
*/
object KeyValue08_mapValues {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (1, "d"), (2, "b"), (3, "c")))
//3.2
rdd.mapValues(_ + "$").collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(1,a$)
(1,d$)
(2,b$)
(3,c$)
3.11 join()连接
1)函数签名:
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))]
2)功能说明
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,
(V,W))的RDD
3)需求说明:创建两个pairRDD,并将key相同的数据聚合到一个元组。
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:06
*/
object KeyValue09_join {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//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)))
rdd.join(rdd1).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(1,(a,4))
(2,(b,5))
3.12 cogroup()类似全连接,但是在同一个RDD中对key聚合
1)函数签名:
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
2)功能说明
在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
操作两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。
3)需求说明:创建两个pairRDD,并将key相同的数据聚合到一个迭代器。
4)代码实现
package com.xiao_after.key_value
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author xiaohu
* @create 2020-09-25 1:06
*/
object KeyValue10_cogroup {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3.具体业务逻辑
//3.1 创建一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c"), (4, "d")))
//3.2 创建第二个RDD
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (3, 6), (5, 6)))
//3.3
rdd.cogroup(rdd1).collect().foreach(println)
//4.关闭连接
sc.stop()
}
}
5)运行结果
(1,(CompactBuffer(a),CompactBuffer(4)))
(2,(CompactBuffer(b),CompactBuffer(5)))
(3,(CompactBuffer(c),CompactBuffer(6)))
(4,(CompactBuffer(d),CompactBuffer()))
(5,(CompactBuffer(),CompactBuffer(6)))