Spark中map相关算子及区别
- map算子:map[U](f: (T) => U): RDD[U]
是对RDD中的每个元素都执行一个指定的函数开产生一个新的RDD。
示例:
val conf = new SparkConf().setAppName("WC").setMaster("local[*]")
val sc = new SparkContext(conf)
val lineRDD = sc.makeRDD(1 to 10)
.map(data => {
data * 2
})
lineRDD.foreach(println)
sc.stop()
结果:
6
12
8
14
10
2
4
20/05/21 11:01:30 INFO Executor: Running task 3.0 in stage 0.0 (TID 3)
16
18
20
- flatMap算子:flatMap[U](f: (T) => TraversableOnce[U]): RDD[U]
类似于map,每一个输入元素可以被映射为0个或多个输出元素,相当于扁平化操作
示例:
val listRDD: RDD[List[Int]] = sc.makeRDD(Array(List(1, 2, 3, 4, 5), List(6, 7, 8, 9, 10)))
val value: RDD[Int] = listRDD.flatMap(datas => datas.map(_ * 2))
value.foreach(println)
结果:
12
14
16
18
20
2
4
6
8
10
将list集合中的数据进行扁平化操作,最后输出。
- mapPartitions算子:mapPartitions[U](f: (Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false): RDD[U]
与map算子类似,但能够独立的在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]。假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区。
示例:
val listRDD = sc.makeRDD(1 to 10)
val mapPartition = listRDD.mapPartitions(datas => datas.map(_ * 2))
mapPartition.collect().foreach(println)
sc.stop()
结果:
2
4
6
8
10
12
14
16
18
20
分析:从上面的结果来看,mapPartitions算子看似与map一样,但是如果从底层的源码来看,还是有一定的区别的,我们知道,RDD中有map,同样在Scala中也有map,我们打开mapPartitions的源码
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U] = withScope {
val cleanedF = sc.clean(f)
new MapPartitionsRDD(
this,
(context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(iter),
preservesPartitioning)
}
这里面的参数是Interator,而Interator是Scala中可迭代的集合,所以代码中datas => datas.map(_ * 2)是Scala中的map操作,不是RDD中的map算子。在RDD的map中,计算的操作是_*2,计算在Executor中执行,所master会把10个数据分给不同的Executor执行,这样就会消耗大量的时间。而在mapPartitions中因为map是属于Scala的,所以datas.map(_ * 2)这样的整体是一个计算操作,所以master会把这样的整体发给Executor中,而datas是每一块分区的数据,所以mapPartitions算子会对每一块分区进行数据,这样会减少网络资源的消耗,效率优于map算子。
缺点:如果一个partition有很多数据的话,由于计算过后,会存在引用,所以内存不会释放,这样就可能会导致OOM。普通的map一般不会。