scala RDD算子

scala RDD转换算子

map

​ 当使用map时,对于List(1,2,3,4),前面分区后会分成两个区

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_map {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    val mapRDD1= rdd.map(
      num => {println("------" + num)
        num
      }
    )
    val mapRDD2= mapRDD1.map(
      num => {println("******" + num)
        num
      }
    )
    mapRDD2.collect()
    sc.stop()
  }
}

在这里插入图片描述

​ 这样的结果是完全无序的,没有规律可循,但是当把分区只分为一个时

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_map {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),1)

    val mapRDD1= rdd.map(
      num => {println("------" + num)
        num
      }
    )
    val mapRDD2= mapRDD1.map(
      num => {println("******" + num)
        num
      }
    )
    mapRDD2.collect()
    sc.stop()
  }
}

在这里插入图片描述

​ 可见,分区内数据的执行是有序的,只有前面一个数据全部的逻辑执行结束之后,才会切换到下一个数据,因此,利用map时,分区之间的执行时并行执行的,而分区内的数据执行是类似串行执行!

mapPartitions

由于map对于分区间是并行执行的,而分区内是类似串行执行,如果需要把一个分区看成一个数据对象,此时就需要用到mapPartitions,mapPartitions会以一个分区为单位进行数据转换操作

package com.atguigu.bigdata.spark.core.rdd.builder

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

import scala.Console.println

object rdd_partiton_mapPartitions {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    val mapRDD1= rdd.mapPartitions(
      iter => {
        println("------")
        iter.map(_*2)
      }
    )
    mapRDD1.collect.foreach(println)
    sc.stop()
  }
}

在这里插入图片描述

但是mapPartitions有一个缺点就是:处理数据的时候会将整个分区的数据加载到内存中引用,处理完的数据是不会被释放掉的,因为存在引用,在内存较小,数据量较大时容易出现内存溢出因此,我们需要保证一个前提:完成比完美更重要

但是当需要对分区内数据做一些操作如比较大小时,mapPartitions的用处要比map好得多,要记住iterator=>iterator

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    val mapRDD1= rdd.mapPartitions(
      iter => {
        List(iter.max).iterator
      }
    )

mapPartitionsWithindex

当我们需要对某一分区进行操作时,map和mapPartitions将无从下手,因此出现了mapPartitionsWithindex用于判断分区号

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

    val mapRDD1= rdd.mapPartitionsWithindex(
      (index,iter) => {
      	iter.map(
      		num => {
      			(index,num)
      	    }//用于表示List内的值所属的分区是什么
      	)
      }
    )

在这里插入图片描述

flatmap

当list中存在其他list,我们无法直接获取到内部list的数据,因此需要利用flatmap对外部list进行扁平化操作

package com.atguigu.bigdata.spark.core.rdd.builder

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_flatmap {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd = sc.makeRDD(
      List(
        List(1,2),3,List(3,4)
      )
    )
    val flatRDD = rdd.flatMap(
      data => data match {
        case list: List[_] => list
        case dat => List(dat)
      }
    )
    flatRDD.collect().foreach(println)

    val rdd1 = sc.makeRDD(List(
      "HELLO WORLD", "HELLO SPARK"
      )
    )
    val flatRDD1 = rdd1.flatMap(
      word => word.split(" ")
    )
    flatRDD1.collect().foreach(println)

    sc.stop()
  }
}

在这里插入图片描述

glom

将分区内的元素组成一个array,前面的flatmap是将List => Int,这里的glom则是将Int => array

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_glom {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val glomRDD: RDD[Array[Int]] = rdd.glom()
    glomRDD.collect().foreach(data => println(data.mkString(",")))
    sc.stop()
  }
}

在这里插入图片描述

结果可见,分区一中的1,2被组成一个新的array,而分区二中的3,4被组成一个新的array

    val sc = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val glomRDD: RDD[Array[Int]] = rdd.glom()
    val value = glomRDD.map(
      _.max
    )
    println(value.collect().sum) //这里将每个分区内最大的数相加

结果是6

groupby

通过匹配key来分组,但是这里的分组是将数据打散,重新组合,这个操作叫做shuffle,极限情况下,数据可能会被分在同一个分区中,但是一个分区可以存在多个组,所以分区和分组没有固定的关系

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_groupby {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[String] = sc.makeRDD(List("HELLO","SPARK","Hello","Scala"), 2)

    val groupRDD: RDD[(Char, Iterable[String])] = rdd.groupBy(_.charAt(0))

    groupRDD.collect().foreach(println)

    sc.stop()
  }
}

filter

filter是对数据进行过滤,以一些条件去筛选我们想要的数据

sample

在shuffle时,可能会出现极端情况即所有数据都被分到同一个分区中也就是数据倾斜,而每一个分区对应一个task,会导致任务执行很慢,所以需要利用sample去随机抽取数据用于判断在这个分区中是什么数据影响了或者说是哪个分组的key影响了 如果在抽取的数据中发现了某个分组的key概率很大,那么就可以在shuffle的时候做一些操作,避免数据倾斜,影响运行效率

distinct

去重操作,但底层代码并不是利用hashset,而是利用

map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)

List(1,2,3,4,1,2,3,4) => (1,null),(2,null),(3,null),(4,null),(1,null),(2,null),(3,null),(4,null)

(1,null) (1,null) => (1,(null,null)) => (1,null) => 1

coalesce

缩减分区时,将参数调小,如果在我们filter数据之后,两个分区的数据量很小,但是计算分区是需要两个executor去执行,会造成资源浪费,但是不能拆分分区内数据,例如(1,2,3,4,5,6)本来分为三个分区,但是我们利用这个算子分为两个分区,则第二个分区数据会是(3,4,5,6),但是该算子会导致数据倾斜,也就是某一分区内数据过多,导致executor压力太大,因此在算子的第二个参数可以选择是否进行shuffle,来打散并重新组合数据

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_coalesce {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3)
    val coalRDD = rdd.coalesce(2,true)
    coalRDD.saveAsTextFile("output")
    sc.stop()
  }
}

扩大分区时,将参数调大,但是一定需要shuffle操作,否则数据不会被打散重新组合,是没有意义的 val coalRDD = rdd.coalesce(6,true) //扩大分区
但是一般扩大分区并不使用这种方法,直接用repartition,可以发现底层源码也是利用coalesce方法

sortBy

根据制定规则对数据源中的数据进行排序,默认是升序,当把参数改为false则为降序,默认情况下,sortBy是不会改变分区数量的,但是会自动shuffle

交集 并集 差集 拉链

拉链就是将对应位置的数据组合成一个一新的数组 ,交集、并集、差集要求两个操作的数据源数据类型要保持一致,但是拉链不需要

拉链要求两个数据源分区数量要保持一致,且分区内的数据数量也要保持一致


import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_交并差拉链 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd1= sc.makeRDD(List(1,2,3,4))
    val rdd2= sc.makeRDD(List(3,4,5,6))

    //交
    val rdd3 = rdd1.intersection(rdd2)
    println(rdd3.collect().mkString(","))
    //并
    val rdd4 = rdd1.union(rdd2)
    println(rdd4.collect().mkString(","))
    //差
    val rdd5 = rdd1.subtract(rdd2)
    val rdd6 = rdd2.subtract(rdd1)
    println(rdd5.collect().mkString(","))
    println(rdd6.collect().mkString(","))
    //拉链
    val rdd7 = rdd1.zip(rdd2)
    println(rdd7.collect().mkString(","))

    sc.stop()
  }
}

在这里插入图片描述

partitionBy

对分区内的数据进行重分区,但是要对key-value数据有效,当重分区的分区器和当前的分区器一样的时候,会什么都不操作

import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}

object rdd_partiton_partitionBy {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd= sc.makeRDD(List(1,2,3,4),2)
    val mapRDD: RDD[(Int, Int)] = rdd.map((_, 1))
    //重分区
    mapRDD.partitionBy(new HashPartitioner(2)).saveAsTextFile("output")
    sc.stop()
  }
}

reduceByKey

对于键值对,当我们处理好计算之后,需要进行合并结果,例如(“1”,1),(“1”,2),(“2”,3),(“2”,1),将相同的key的value加和,但是(“1”,1),(“1”,2),(“1”,4)这种数据,会进行两两相加,1+2=3,3+4=7,而当只有一个key时,则不会进行计算,直接出结果

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_reduceByKey {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd= sc.makeRDD(List(("1",1),("1",2),("2",3),("2",1)))
    val reduceRDD: RDD[(String, Int)] = rdd.reduceByKey((x: Int, y: Int) => (x + y))
    reduceRDD.collect().foreach(println)
    sc.stop()
  }
}

groupByKey和groupBy

groupByKey是对键值对以相同的key分组,如(“1”,1),(“1”,2),(“2”,3),(“2”,1)分为(1,CompactBuffer(1, 2)),(2,CompactBuffer(3, 1)),而groupBy也是差不多的意思,但是分组后的value不是分组前value的组合,而是所有数据,(1,CompactBuffer((1,1), (1,2))),(2,CompactBuffer((2,3), (2,1))),且groupBy的参数是不确定的,可以自己定义

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_groupByKey {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd= sc.makeRDD(List(("1",1),("1",2),("2",3),("2",1)))
    val reduceRDD = rdd.groupByKey()
    reduceRDD.collect().foreach(println)

    val reduceRDD1 = rdd.groupBy(_._1)
    reduceRDD1.collect().foreach(println)
    sc.stop()
  }
}

在这里插入图片描述

groupByKey和reduceByKey

groupByKey是在分组后聚合,而reduceByKey在分组前先聚合,在分组后再聚合


在这里插入图片描述
在这里插入图片描述

aggregateByKey

在分区内先进行比较,由于有两个参数,第一个参数是填写进行比较的值,第二个参数内也有两个参数,第一个参数是分区内的操作,第二个是分区间的操作
在这里插入图片描述
在这里插入图片描述

import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_aggregateByKey {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd= sc.makeRDD(List(("a",1),("a",2),("b",3),("b",4),("b",5),("a",6)),2)
    val aggregateRDD = rdd.aggregateByKey(0)(//进行比较的值
      math.max,//分区内操作
      _+_//分区间操作
    )
    aggregateRDD.collect().foreach(println)
    sc.stop()
  }
}

在这里插入图片描述

foldByKey

当分区内和分区间的规则是一样的时候,可以使用上面的aggregateByKey,但是spark直接用foldByKey就可以了

val foldRDD= rdd.foldByKey(0)(_+_).collect().foreach(println)

combineByKey

combineByKey有三个参数,第一个参数代表将相同Key的第一个数据进行结构转换,第二个参数表示分区内的操作,第三个参数表示分区间的操作

如果求上面数据的平均值,此时需要利用两个参数去操作,第一个用于传同个key的value总和,第二个用于求次数,我们可以直接用combineByKey将数据源中的第一个数据做一些修改作为第一个参数,也就是不需要利用
(0,0)去做第一个数,我们直接用数据带入就可以了

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_combineByKey {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd= sc.makeRDD(List(("a",1),("a",2),("b",3),("b",4),("b",5),("a",6)),2)
    val combRDD = rdd.combineByKey(
      v => (v, 1),//把数据源中的value改为(value,1),用于计算value相加和次数统计
      (t:(Int,Int), v) => (t._1 + v, t._2 + 1),//组内操作
      (t1:(Int,Int), t2:(Int,Int)) => (t1._1 + t2._1, t1._2 + t2._2)//组间操作
    )
    val resultRDD: RDD[(String, Int)] = combRDD.mapValues {
      case (num, cnt) => {
        num / cnt
      }
    }
    resultRDD.collect().foreach(println)
    sc.stop()
  }
}

join

对于两个List,是属于k-v类型的数据源,join算子会将相同key的value组成一个元组,类似SQL的内连接:
在这里插入图片描述
当另一个元组中有多个数据的key和第一个元组中的相同时,可能会出现笛卡尔积的效应:


import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_join {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd1= sc.makeRDD(List(("a",1),("b",3),("c",4)))
    val rdd2= sc.makeRDD(List(("a",3),("b",5),("a",6),("a",4)))
    val joinRDD: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
    joinRDD.collect().foreach(println)
    sc.stop()
  }
}

在这里插入图片描述
即rdd1中的(“a”,1)会和rdd2中的(“a”,3),(“a”,6),(“a”,4)相匹配,因此,当两个数据源中相同key的数据过多时,会依次匹配,数据量会呈几何增长,降低效率,因此尽量不要使用join.

leftOuterJoin && rightOuterJoin

就是SQL中的左外连接和右外连接,当以左外连接时,以左表为主,相反则以右边为主,

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_leftOuterJoin_rightOuterJoin {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd1= sc.makeRDD(List(("a",1),("b",3),("c",4)))
    val rdd2= sc.makeRDD(List(("a",3),("b",5),("c",6),("d",4)))
    val leftOuterRDD: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)
    val rightOuterRDD: RDD[(String, (Option[Int], Int))] = rdd1.rightOuterJoin(rdd2)
    leftOuterRDD.collect().foreach(println)
    println("--------------")
    rightOuterRDD.collect().foreach(println)
    sc.stop()
  }
}

在这里插入图片描述

cogroup

connect+group将两个数据源中相同key的value组合起来,并不是右外连接,如果本数据源中有相同key的数据,那么就会先把本数据源的数据先组合,再去和其他数据源的数据做cofgroup

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object rdd_partiton_cogroup {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().set("spark.testing.memory","2147480000").setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(sparkConf)

    val rdd1= sc.makeRDD(List(("a",1),("b",3),("c",4)))
    val rdd2= sc.makeRDD(List(("a",3),("a",4),("b",5),("c",6),("d",4)))
    val coRDD: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)

    coRDD.collect().foreach(println)
    sc.stop()
  }
}

在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值