Spark-算子

本文详细介绍了Spark中的Transform算子和Action算子,包括它们的分类和具体示例。Transform算子包括map、flatMap、mapPartitions等,Action算子如count、saveAsTextFile等。这些算子在Spark数据处理中扮演关键角色,Transform算子延迟执行,Action算子触发实际计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        Spark的Transform算子和Action算子列举和示例


一:Spark的算子的分类        

  1. 从大方向来说,Spark 算子大致可以分为以下两类:
    1)TransFormation:变换/转换算子(懒加载):不触发提交作业,完成作业中间过程处理。
        TransFormation操作是延迟计算的,也就是说从一个RDD转换生成另一个RDD的转换操作
        不是马上执行,需要等到有 Action 操作时才会真正触发运算。


    2)Action行动算子:这类算子会触发 SparkContext 提交job作业。
        Action算子会触发Spark提交作业(Job),并将数据输出Spark系统。
    从小方向来说,Spark算子大致分以下三类:
    1)Value数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Value型的数据。
    2)Key-Value数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Key-Value型的数据对。
    3)Action算子,这类算子会触发SparkContext提交Job作业。
    
1)Value 数据类型的Transformation算子
    一丶输入分区与输出分区一对一
        1、map算子
        2、flatMap算子
        3、mapParitions算子
        4、glom算子
    二、输入分区与输出分区多对一
        5、union算子
        6、cartesian算子
    三、输入分区与输出分区多对多
        7、groupBy算子
    四、输出分区为输入分区子集
        8、filter算子
        9、distinct算子
        10、subtract算子
        11、sample算子
        12、takeSample算子
    五、Cache
        13、cache算子
        14、persist算子
2)key-Value数据类型的Transformation算子
    一、输入分区与输出分区一对一
        15、mapValues算子
    二、对单个RDD或两个RDD聚集
        单个RDD聚集:
        16、combinerByKey算子
        17、reduceByKey算子
        18、partitionBy算子
        两个RDD聚集
        19、Cogroup算子
    三、连接
        20、join算子
        21、leftOutJoin和rightOutJoin算子
3)Action算子
    一:无输出
        22、foreach算子
    二:HDFS
        23、saveAsTextFile算子
        24、saveAsObject算子
    三:Scala集合和数据类型
        25、collect算子
        26、collectAsMap算子
        27、reduceByKeyLocally算子
        28、lookup算子
        29、count算子
        30、top算子
        31、reduce算子
        32、fold算子
        33、aggregate算子
        
1.Transformations 算子
--map
    将原来RDD的每个数据项通过map中的用户自定义函数f映射转变为一个新的函数.    
 def map(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5), (3))

    println(rdd.map(x => x * 100).collect().toBuffer)
    //Res:ArrayBuffer(100, 200, 300, 400, 500)
  }


--flatMap
将原来RDD中的每个元素通过函数转换为新的元素,并将生成的RDD的每个集合中的元素合并为一个集合。
 
 def flatMap(sc: SparkContext) = {
    val rdd: RDD[String] = sc.parallelize(Array("ab","cd","ef"))
    println( rdd.flatMap(x => x+"@").collect().toBuffer)
    rdd.flatMap(x=>x+"#").foreach(print(_))

    //res:ArrayBuffer(a, b, @, c, d, @, e, f, @)
    //res:ab#cd#ef#
  }

--mapPartitions
    mapPartitions函数获取到每个分区的迭代器,在函数中通过这个分区整体的迭代器对整个分区的元素进行操作。
    效果和map一样,只是操作对象是一个迭代器。对分区的操作。map是对每个元素的操作。
def mapPartitions(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6), (3))
    //rdd.mapPartitions(it => it.filter(_>3)).collect().toBuffer
    println(rdd.mapPartitions(it => it.filter(_ > 3)).collect().toBuffer)
    rdd.mapPartitions(it => it.filter(_ < 8)).foreach(print(_))
    //Res:314256
    //Res: ArrayBuffer(4, 5, 6)
  }
  
 --glom
    glom函数将每个分区形成一个数组,内部实现是返回的GlommedRDD.
def glom(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6), 2)
        println(rdd.glom().collect.toBuffer)
    
  //Res:ArrayBuffer([I@766a4535, [I@63718b93)
}

--union
    使用union函数时需要保证两个RDD元素的类型相同,返回的RDD数据类型和合并的RDD元素数据类型相同,
    并不进行去重操作,保存所有元素。
 def union(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(Array(1,2,3,4),3)
    val unionRdd: RDD[Int] = rdd.union(rdd).union(rdd)
    //输出分区数
    println(unionRdd.partitions.size)
   // unionRdd.foreach(print(_))
    //Res:9
  }
 
--cartesian 
    对两个RDD内的所有元素进行笛卡尔积操作。操作后,内部实现返回CartesianRDD
def cartesian(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(Array(1,2))
    println(rdd.cartesian(rdd).partitions.size)
    println(rdd.cartesian(rdd).collect().toBuffer)
    //res:4
    //res:ArrayBuffer((1,1), (1,2), (2,1), (2,2))
  }

--groupBy 
    将元素通过函数生成相应的key,数据就转化为key-Value格式,之后将key相同的元素分为一组。
  def groupBy(sc: SparkContext): Unit = {
    val rdd = sc.parallelize(1 to 10, 3)
    println(
      rdd.groupBy(x => {
        if (x % 2 == 0) " even" else "odd"
      }).collect.toBuffer)
//Res:ArrayBuffer(( even,CompactBuffer(2, 4, 6, 8, 10)), (odd,CompactBuffer(1, 3, 5, 7, 9)))
  }

--filter 
    filter函数功能是对元素进行过滤,对每个元素应用函数,返回值为true的元素在RDD中
    保留,返回值为false的元素进行过滤
def filter(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(Array(1,2,3,4),3)
      println(rdd.filter(_>=3).collect().toBuffer)
    //Res:ArrayBuffer(3, 4)
  }
  
--distinct 
    distinct 将RDD中的元素进行去重操作。
  def distinct(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(Array(1,2,3,3,2,1,1,0,2,4,2,3),3)
    println(rdd.distinct().collect().toBuffer)
    //Res: ArrayBuffer(0, 3, 4, 1, 2)
  }
  
 --subtract
    subtract相当于进行集合的差操作,RDD 1 去除RDD 2 交集中的所有元素。
  def subtract(sc: SparkContext) = {
    val rdd1: RDD[Int] = sc.parallelize(1 to 9, 3)
    val rdd2: RDD[Int] = sc.parallelize(1 to 3, 3)
    println(rdd1.subtract(rdd2).collect().toBuffer)
    //Res:ArrayBuffer(6, 9, 4, 7, 5, 8)
  }

--sample 
    sample将RDD这个集合内的元素进行采样,获取所有元素子集。
    用户可以设定是否有放回的抽样、百分比、随机种子,进而决定采样方式。
    内部实现是生成 SampledRDD(withReplacement, fraction, seed).
    函数参数设置: 
    withReplacement=true,表示有放回的抽样。 
    withReplacement=false,表示无放回的抽样 
    Fraction=>,一个大于0,小于或等于1的小数值,用于控制要读取的数据所占整个数据集的概率。 
    Seed=>,这个值如果没有传入,默认值是一个0~Long.maxvalue之间的整数。
  def sample(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(1 to 10000,3)
    println(rdd.sample(false,0.1,0))
    println(rdd.sample(false,0.1,0).collect().toBuffer)
    //Res:PartitionwiseSampledRDD[1] at sample at DemoSZ.scala:88
    //Res:................................
  }    

--takeSample 
    takeSample()函数和上面的sample函数是一个原理,但是不使用相对比例采样,
    而是按设定的采样个数进行采样,同时返回结果不再是RDD,而是相当于对采样后的数据进行Collect()
    
--cache 
    cache 将 RDD 元素从磁盘缓存到内存。 相当于 persist(MEMORY_ONLY) 函数的功能

--persist 
    persist 函数对 RDD 进行缓存操作。数据缓存在哪里依据 StorageLevel 这个枚举类型进行确定。 
    有以下几种类型的组合, DISK 代表磁盘,MEMORY 代表内存, SER 代表数据是否进行序列化存储。 
    下面为函数定义, StorageLevel 是枚举类型,代表存储模式,可以通过按需进行选择。
    persist(newLevel:StorageLevel).图 中列出persist 函数可以进行缓存的模式。例如,
    MEMORY_AND_DISK_SER 代表数据可以存储在内存和磁盘,并且以序列化的方式存储,其他同理。 
  def persist(sc: SparkContext) = {
    val rdd: RDD[Int] = sc.parallelize(1 to 100, 3)
    //println(rdd.persist(StorageLevel.MEMORY_AND_DISK))
    // println(rdd.persist(StorageLevel.MEMORY_AND_DISK).collect().toBuffer)
    //Res:......................
  }
  
----repartition和coalesce 
    repartition(numPartitions:Int):RDD[T] 
    coalesce(numPartitions:Int,shuffle:Boolean=false):RDD[T]
    他们两个都是RDD的分区进行重新划分,repartition只是coalesce接口中shuffle为true的简易实现,
    (假设RDD有N个分区,需要重新划分成M个分区)
    1.如果 N小于M 一般情况下N个分区有数据分布不均匀的状况,利用HashPartitioner函数将数据重新分区为M个,
     这时需要将shuffle设置为true。 
    2.如果N>M并且N和M相差不多,(假如N是1000,M是100)那么就可以将N个分区中的若干个分区合并成一个新的分区,
     最终合并为M个分区,这时可以将shuff设置为false,在shuffl为false的情况下,如果M>N时,coalesce为无效的,
     不进行shuffle过程,父RDD和子RDD之间是窄依赖关系。 
    3.如果N>M并且两者相差悬殊,这时如果将shuffle设置为false,父子RDD是窄依赖关系,
     他们同处在一个Stage中,就可能造成Spark程序的并行度不够,从而影响性能,如果在M为1的时候,
     为了使coalesce之前的操作有更好的并行度,可以讲shuffle设置为true。 
     总之:如果shuff为false时,如果传入的参数大于现有的分区数目,RDD的分区数不变,
     也就是说不经过shuffle,是无法将RDDde分区数变多的。

  
 Transform里面的Key-Value形式的RDD
 
 --mapValues
    mapValues是针对(key,value)型数据中的Value进行Map操作,而不对Key进行处理
  def mapValues(sc: SparkContext) = {
    val rdd: RDD[(String, Int)] = sc.parallelize(Array(("a",1),("b",2),("c",3)),3)
    //rdd.mapValues{_*10}.foreach(println(_))
    rdd.mapValues{_*10}.foreach(println)
  //Res:(a,10)  (b,20)  (c,30)
  }
  
--combineByKey 
    combineByKey[C]( 
    createCombiner:(V) C, 
  mergeValue:(C, V) C, 
  mergeCombiners:(C, C) C, 
  partitioner:Partitioner, 
  mapSideCombine:Boolean=true, 
  serializer:Serializer=null):RDD[(K,C)] 
    说明:数据为RDD[K,V] 
    createCombiner:是将V值类型转为C类型. 
    mergeValue:将C和V进行操作(理解为单分区里面的操作) 
    mergeCombiners:将C和C进行操作//理解为分区与分区直接的操作 
    partitioner:分区方式 
    mapSideCombine:是否在map端先进行操作 
    serializer:序列号方式
def combineByKey(sc: SparkContext) = {
    val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0,5),(0,4),(2,3),(2,2),(0,1)),3)
    val createCombiner=(a:Int)=>List(a)//将5转换成List(5)
    val mergeValue=(a:List[Int],b:Int)=>a.::(b)//将List(5)和4操作成List(5,4)
    val mergeCombiners=(a:List[Int],b:List[Int])=>a.:::(b)//将List(4,5)和List(1)操作得到List(1,4,5)
    println(rdd.combineByKey(createCombiner,mergeValue,mergeCombiners).collect().toBuffer)
    //Res:ArrayBuffer((0,List(1, 4, 5)), (2,List(2, 3)))
  }

--reduceByKey 
    做WordCount的时候用到,按照可以来做一个聚合操作。底层是
    combineByKey。只是那个C跟V是一个类型。
    API:reduceByKey(partitioner: Partitioner, func: (V, V) => V)
    
def reduceByKey(sc: SparkContext) = {
    val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0, 5), (0, 4), (2, 3), (2, 2), (0, 1)), 3)
    println(rdd.reduceByKey((a, b) => a + b).collect.toBuffer)
    //Res:ArrayBuffer((0,10), (2,5))
  }

 --partitionBy 
    重分区partitionBy(Partitioner)
def partitionBy(sc: SparkContext) = {
    val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0,5),(0,4),(2,3),(2,2),(0,1)), 3)
    val redRdd: RDD[(Int, Int)] = rdd.partitionBy(new HashPartitioner(10))
    //redRdd: RDD[(Int, Int)] = rdd.partitionBy(new HashPartitioner(10)) //重新分为10个区
    //println(rdd.partitionBy(new HashPartitioner(10)).collect.toBuffer)
    println(redRdd.getNumPartitions)//10
  }
  
--Cogroup
    返回两个Rdd相同key的各自数据集合
    cogroup[W](other: RDD[(K, W)],
    numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))] 
    
def cogroup(sc: SparkContext) = {
    val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0, 5), (0, 4), (2, 3), (2, 2), (0, 1)), 3)
    rdd.cogroup(rdd).foreach(println) //
    //(2,(CompactBuffer(3, 2),CompactBuffer(3, 2)))
    //(0,(CompactBuffer(5, 4, 1),CompactBuffer(5, 4, 1)))
  }    
  
 --join
    两个RDD做join操作
    RDD[(K,V)].join(other: RDD[(K, W)]): RDD[(K, (V, W))] 
    如果join不到的就丢弃
  def join(sc: SparkContext) = {
    val rdd = sc.parallelize(Array((0, 1), (0, 2), (2, 3)), 3)
    val rdd2 = sc.parallelize(Array((0, 11), (0, 22), (3, 33)), 3)
    rdd.join(rdd2).foreach(println)
    //(0,(1,11))
    //(0,(1,22))
    //(0,(2,11))
    //(0,(2,22))
  }

  
--leftOutJoin和rightOutJoin 
    leftOutJoin:join过程中,如果找不到相同的key。
    则还是保留左边RDD中的数据;
    RDD[(K,V)].join(other: RDD[(K, W)]): RDD[(K, (V, Option[W])]

Action算子
        以Count为例:点进count的源码可以看到,程序提交了一个job 
        def count(): Long = sc.runJob(this, Utils.getIteratorSize _).sum
  
--count 
    返回rdd的元素个数    
--foreach 
    对rdd的每个元素一次做操作,但没有返回值,最常用的就是 
    foreach(println)
    
--saveAsTextFile 
    函数将数据输出,存储到 HDFS 的指定目录。
    将 RDD 中的每个元素映射转变为 (null, x.toString),
    然后再将其写入 HDFS。图 中左侧方框代表 RDD 分区,
    右侧方框代表 HDFS 的 Block。通过函数将RDD 的每个分区存储为
    HDFS 中的一个 Block 
    
--saveAsObjectFile 
    saveAsObjectFile将分区中的每10个元素组成一个Array,然后将这个Array序列化,
    映射为(Null,BytesWritable(Y))的元素,写入HDFS为SequenceFile的格式。

--collect 
    将RDD的数据收集到driver端。数据量大的数据会报OOM.不建议使用,通常用take(num:Int)来代替。
    
--collectAsMap 
    collectAsMap对(K,V)型的RDD数据返回一个单机HashMap。
    对于重复K的RDD元素,后面的元素覆盖前面的元素。
    

    
--lookup 
    lookup(key:K):Seq[V] 
    Lookup函数对(Key,Value)型的RDD操作,返回指定Key对应的元素形成的Seq。
    这个函数处理优化的部分在于,如果这个RDD包含分区器,则只会对应处理K所在的分区,
    然后返回由(K,V)形成的Seq。 如果RDD不包含分区器,则需要对全RDD元素进行暴力扫描处理,
    搜索指定K对应的元素。
    
--top 
    top(num:Int)(implicit ord:Ordering[T]):Array[T] 
    //自定义一个排序规则(倒序) 
    object MyOrdering extends Ordering[Int] { 
    def compare(a:Int, b:Int) = b compare a 
    } 
    top(10)(MyOrdering )
    top返回最大的k个元素。 
    take返回前k个元素。
    
--reduce 
    将数据及合成一个元素
        
--fold 
    fold和reduce的原理相同,但是与reduce不同,相当于每个reduce时,
    迭代器取的第一个元素是zeroValue
 val a=sc.parallelize(List(1,2,3),3)
    println(a.fold(0)(_+_))
    println(a.fold(10)(_+_))
    //Res:6
    //Res:46

--- aggregate 
    aggregate先对每个分区的所有元素进行aggregate操作,再对分区的结果进行fold操作。 
  aggreagate与fold和reduce的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,
    这种聚集是并行化的。 而在fold和reduce函数的运算过程中,每个分区中需要进行串行处理,
    每个分区串行计算完结果,结果再按之前的方式进行聚集,并返回最终聚集结果。
    aggregate[B](z: B)(seqop: (B,A) => B,combop: (B,B) => B): B 
    
 val z = sc.parallelize(List(1, 2, 3, 4, 5, 6), 2)

    // lets first print out the contents of the RDD with partition labels
    def myfunc(index: Int, iter: Iterator[(Int)]): Iterator[String] = {
      iter.toList.map(x => "[partID:" + index + ", val: " + x + "]").iterator
    }

    println(z.mapPartitionsWithIndex(myfunc).collect.toBuffer)
    //ArrayBuffer([partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3],
    // [partID:1, val: 4], [partID:1, val: 5], [partID:1, val: 6])
    println(z.aggregate(0)(math.max(_, _), _ + _)) //9
    
    


    

    
    
    


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值