RDD 的操作分为转化(Transformation)操作和行动(Action)操作。转化操作就是从一个 RDD 产生一个新的 RDD,而行动操作就是进行实际的计算。转换操作和行动操作一般也被叫做转换算子和行动算子。
RDD 的操作是惰性的,当 RDD 执行转化操作的时候,实际计算并没有被执行,只有当 RDD 执行行动操作时才会促发计算任务提交,从而执行相应的计算操作。
RDD转换算子
首先,我们需要理解一件事。这种算子所输入的往往是一个或多个函数,这个函数用来对转换算子所指定的数据做具体处理。例如:
//这里使用map使得rdd的每条数据都*2,所以输入和输出数据都是单条数据。
def mapFunction(num:Int): Int = {
num * 2
}
val mapRDD: RDD[Int] = rdd.map(mapFunction)
输入的具体函数根据我们的业务决定。很多情况下,这种函数可以简化成匿名函数。
val mapRDD: RDD[Int] = rdd.map(_*2)
具体的简化流程可以看我的另外一篇文章:Scala的匿名函数在map()上的简化。
那么接下来,我们开始讲解一些经典的算子。
RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value类型。
Value类型
1. map
对RDD中的数据进行逐条转换,有点像MR的map操作。
例如,我们对List中所有的数都*2。
val dataRDD: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4))
val mapRDD: RDD[Int] = dataRDD.map(_*2)
/*
结果:
2
4
6
8
*/
2. mapPartitions
和map()不同,mapPartitions()将数据以分区为单位进行处理。匿名函数的输入是一个分区内数据的迭代器,输出也要求是一个迭代器。
这里将每个分区的数据用迭代器的方式输出到控制台。
val dataRDD: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4), 2)
val mapRDD: RDD[Int] = dataRDD.mapPartitions(
iters => {
while(iters.hasNext){
println(iters.next())
}
iters
}
)
/*
结果:
3
1
2
4
这里可以看到,由于分成了两个区,变成了(1,2)(3,4)两个区
1和3是不同分区,所以不一定有固定顺序。但是3一定在4前面,1一定在2前面
*/
这里像map()一样,我们对List中所有的数都*2。
//第二个参数2表示分区数量为2
val dataRDD: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4), 2)
val mapRDD: RDD[Int] = dataRDD.mapPartitions(
iters => {
iters.map(_*2)
}
)
/*
结果:
2
4
6
8
*/
map和mapPartitions的区别
- map对分区内的数据一条一条类似串行处理,而mapPartitions直接输入一个迭代器,可以作类似批处理操作。
- map是对每条数据进行处理,处理前后的数据量不会变化;而mapPartitions返回的是一个迭代器,数量不要求和处理前数量一致,因此可以进行filter等操作。
- map因为进行逐条操作,因此性能较低,相对地,类似批处理的mapPartitions性能较高;但是 mapPartitions 算子会长时间占用内存,那么这样会导致内存可能不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。
3. mapPartitionsWithIndex
mapPartitionsWithIndex和mapPartitions的不同是,mapPartitionsWithIndex除了获得分区对应的迭代器,还可以得到该分区的编号。
val dataRDD: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4)