前言
RDD是spark中最重要的抽象单位,所有的高级操作细化到底层,都是由RDD来完成的,RDD的操作有很多,但从计算的角度来划分,RDD可以分为转换算子和执行算子。
之前我们也讲到,RDD是惰性加载的,转换算子创建时并不会执行计算过程,只有当遇到执行算子时才会产生计算。本文就RDD的转换操作和执行操作作一个总结。
正文
首先创建执行环境和数据集
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("test01")
val sc = new SparkContext(conf)
sc.setLogLevel("ERROR")
val kv1: RDD[(String, Int)] = sc.parallelize(List(
("zhangsan", 11),
("zhangsan", 12),
("lisi", 13),
("wangwu", 14)
))
val kv2: RDD[(String, Int)] = sc.parallelize(List(
("zhangsan", 21),
("zhangsan", 12),
("zhangsan", 22),
("lisi", 23),
("zhaoliu", 28)
))
1、转换算子
1.1 map
-- RDD[U] 将函数应用于RDD的每一元素,并返回一个新的RDD
-- 将RDD中的元素一个一个传入,进行转换计算
kv1.map(v=>{(v._1,v._2+1)}).foreach(println)
结果
(zhangsan,12)
(zhangsan,13)
(lisi,14)
(wangwu,15)
1.2 flatMap
-- 同样是map操作,只是将map操作的数据扁平化了
val list = List("hello thank you","thank you" ,"very much")
val listRDD = sc.parallelize(list)
结果
hello
thank
you
thank
you
very
much
1.3 mapPartitions
-- 对每个分区进行操作,每一个分区运行一次,函数需要接受Iterator类型,然后返回Iterator。
val list = List("hello thank you", "thank you", "very much")
val listRDD = sc.parallelize(list,2)
listRDD.mapPartitions(iterator=>{
val sb:ListBuffer[String] =ListBuffer();
while (iterator.hasNext){
sb.append("111 "+iterator.next())
}
结果
111 hello thank you
111 thank you
111 very much
1.4 filter
-- 设置过滤条件,将tuple2元组中第二列大于13的记录过滤掉
kv1.filter(e=>{(e._2<13)}).foreach(println)
结果
(zhangsan,11)
(zhangsan,12)
1.5 mapPartitionsWithIndex
-- 与mapPartitonsWithIndex功能一样,但是可以获取到分区号
val listRDD = sc.parallelize(list,2)
listRDD.mapPartitionsWithIndex((index,iterator)=>{
val sb:ListBuffer[String] =ListBuffer();
while (iterator.hasNext){
sb.append("分区号:"+index+" 值:"+iterator.next())
}
sb.iterator
}).foreach(word=>println(word))
结果
val listRDD = sc.parallelize(list,2)
listRDD.mapPartitionsWithIndex((index,iterator)=>{
val sb:ListBuffer[String] =ListBuffer();
while (iterator.hasNext){
sb.append("分区号:"+index+" 值:"+iterator.next())
}
sb.iterator
}).foreach(word=>println(word))
1.6 Sample
/**
* sample用来从RDD中抽取样本。他有三个参数
* withReplacement: Boolean,
* true: 有放回的抽样
* false: 无放回抽象
* fraction: Double:
* 抽取样本的比例
* seed: Long:
* 随机种子
*/
val list = 1 to 100
val listRDD = sc.parallelize(list)
listRDD.sample(false,0.1,0).foreach(num => print(num + " "))
结果
10 39 41 53 54 58 60 80 89 98
1.7 union
-- 将kv1和kv2取一个并集
kv1.union(kv2).foreach(println((_)))
结果
(zhangsan,11)
(zhangsan,12)
(lisi,13)
(wangwu,14)
(zhangsan,21)
(zhangsan,22)
(lisi,23)
(zhaoliu,28)
1.8 intersection
-- 将两个RDD取一个并集
-- 为了显示效果,我们在kv2中添加了一条kv1中的记录
kv1.intersection(kv2).foreach(println((_)))
结果
(zhangsan,12)
1.9 distinct
-- 将一个RDD进行去重操作
-- 为了显示效果,我们在kv1中添加了一条重复记录
kv1.distinct().foreach(println(_))
结果
(zhangsan,12)
(lisi,13)
(zhangsan,11)
(wangwu,14)
1.10 partitionBy
-- partitionBy()需要传入一个partitioner
kv1.partitionBy(new org.apache.spark.HashPartitioner(2)).foreach(println)
结果
(zhangsan,11)
(zhangsan,12)
(lisi,13)
(wangwu,14)
1.11 repartiton
-- repartiton需要传入一个numpartition
kv1.repartition(3).foreach(println)
结果
(zhangsan,11)
(zhangsan,12)
(lisi,13)
(wangwu,14)
1.12 join
-- join操作按key分组,最终成为(key,(value1,value2))的形式
val list1 = List((1, "张三"), (2, "李四"), (3, "王五"))
val list2 = List((1, 99), (2, 98), (3, 97))
val list1RDD = sc.parallelize(list1)
val list2RDD = sc.parallelize(list2)
val joinRDD = list1RDD.join(list2RDD)
joinRDD.foreach(t => println("学号:" + t._1 + " 姓名:" + t._2._1 + " 成绩:" + t._2._2))
结果
学号:1 姓名:张三 成绩:99
学号:3 姓名:王五 成绩:97
学号:2 姓名:李四 成绩:98
1.13 reduceBykey
-- reduceBykey按key分组,对key相同的记录做计算
val res: RDD[(String, Int)] = kv1.reduceByKey((x, y) => x + y)
结果
(zhangsan,23)
(wangwu,14)
(lisi,13)
1.14 cartesian
-- cartesian求两个RDD的笛卡尔积
kv1.cartesian(kv2).foreach(println(_))
结果
((zhangsan,11),(zhangsan,21))
((zhangsan,11),(zhangsan,12))
((zhangsan,11),(zhangsan,22))
((zhangsan,11),(lisi,23))
((zhangsan,11),(zhaoliu,28))
((zhangsan,12),(zhangsan,21))
((zhangsan,12),(zhangsan,12))
((zhangsan,12),(zhangsan,22))
((zhangsan,12),(lisi,23))
((zhangsan,12),(zhaoliu,28))
((lisi,13),(zhangsan,21))
((lisi,13),(zhangsan,12))
((lisi,13),(zhangsan,22))
((lisi,13),(lisi,23))
((lisi,13),(zhaoliu,28))
((wangwu,14),(zhangsan,21))
((wangwu,14),(zhangsan,12))
((wangwu,14),(zhangsan,22))
((wangwu,14),(lisi,23))
((wangwu,14),(zhaoliu,28))
1.15 goupBykey
-- groupBykey对key进行分组,转换成(k,CompactBuffer())的形式
kv1.groupByKey().foreach(println)
结果
(zhangsan,CompactBuffer(11, 12))
(wangwu,CompactBuffer(14))
(lisi,CompactBuffer(13))
1.16 sortBykey
-- sortBykey 按key分组从小到大排序
kv1.sortByKey().foreach(println)
结果
(lisi,13)
(wangwu,14)
(zhangsan,11)
(zhangsan,12)
1.17 cogroup
--对两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。
--与reduceByKey不同的是它针对两个RDD中相同的key的元素进行合并。
val rdd1 = sc.parallelize(Array(("aa",1),("bb",2),("cc",6)))
val rdd2 = sc.parallelize(Array(("aa",3),("dd",4),("aa",5)))
rdd1.cogroup(rdd2).foreach(println)
结果
(aa,(CompactBuffer(1),CompactBuffer(3, 5)))
(dd,(CompactBuffer(),CompactBuffer(4)))
(bb,(CompactBuffer(2),CompactBuffer()))
(cc,(CompactBuffer(6),CompactBuffer()))
1.18 coalesce
-- coalesce和repartition都是对RDD进行分区,但是coalesce不会产生shuffle操作,
-- 因此它的分区数也不一定就是用户设置的分区数
val value: RDD[(String, Int)] = kv1.coalesce(5)
println(value.partitions.length)
结果
1
1.19 mapValues
-- mapValues 传入每组数据的value,对其进行计算
kv1.mapValues(x=>x+1).foreach(println)
结果
(zhangsan,12)
(zhangsan,13)
(lisi,14)
(wangwu,15)
1.20 subtract
-- substract 对两个RDD取差集
kv1.subtract(kv2).foreach(println)
结果
(wangwu,14)
(lisi,13)
(zhangsan,11)
2、执行算子
2.1 reduce
-- reduce 操作涉及到新值和老值,两个数计算的结果又作为老值传入reduce函数
val list = List(1,2,3,4,5,6)
val listRDD = sc.parallelize(list)
val result: Int = listRDD.reduce((x, y) => x + y)
println(result)
结果
21
2.1 foreach
-- 在数据集的每一个元素上,运行函数func进行更新。
kv1.foreach(println)
结果
(zhangsan,11)
(zhangsan,12)
(lisi,13)
(wangwu,14)
2.2 takeSample
-- 抽样但是返回一个scala集合。
kv1.takeSample(false,2,0).foreach(println)
结果
(zhangsan,11)
(lisi,13)
2.3 collect
-- 以数组的形式返回数据集的所有元素
val tuples: Array[(String, Int)] = kv1.collect()
tuples.foreach(f=>{println(f._1+" "+f._2)})
结果
zhangsan 11
zhangsan 12
lisi 13
wangwu 14
2.4 count
- 返回RDD中元素的个数
println(kv1.count())
结果
4
2.5 first
-- 返回RDD的第一个元素
println(kv1.first())
结果
(zhangsan,11)
2.6 take
-- 以返回RDD的前n个元素
val tuples: Array[(String, Int)] = kv1.take(2)
tuples.foreach(f=>println(f._1+" "+f._2))
结果
zhangsan 11
zhangsan 12
2.7 takeOrdered
-- 返回前几个的排序
val rdd1: RDD[Int] = sc.parallelize( List( 1,2,0,8,9,3,4,5) )
val ints: Array[Int] = rdd1.takeOrdered(3)
ints.foreach(f=>println(f))
结果
0
1
2
2.8 aggregate
-- 接受多个输入,并按照一定的规则运算以后输出一个结果值
val rdd1: RDD[Int] = sc.parallelize( List( 1,2,2,8,9,3,4,5))
def func1(v1:Int,v2:Int):Int={
v1*v2
}
def func2(v3:Int,v4:Int):Int={
v3+v4
}
println(rdd1.aggregate(1)(func1, func2))
结果
// 首先运行第一个函数func1:初始值为1,所以1*1*2*2*8*9*3*4*5=17280
// 接着运行第二个函数func2:初始值为1,所以1+17280=17281
17281
2.9 fold
-- 与reduce类似,区别就是fold有一个初始值
val rdd1: RDD[Int] = sc.parallelize( List( 1,2,2,8,9,3,4,5))
println(rdd1.fold(0)(func2))
结果
34
2.10 saveAsTextFile
将RDD以文本文件的方式保存到本地或者HDFS中
val rdd1: RDD[Int] = sc.parallelize( List( 1,2,2,8,9,3,4,5))
rdd1.saveAsTextFile("./data/test/test.txt")
结果
test.txt
2.11 countBykey
-- 按key对记录进行统计,结果以Map(key,int)的类型返回
println(kv1.countByKey())
结果
Map(zhangsan -> 2, wangwu -> 1, lisi -> 1)