Spark Core----Spark常用算子

1、Spark代码可以分成三个部分:

        读取数据,得到第一个RDD

        处理数据,RDD之间的转化

        保存数据,将RDD保存到存储系统。

2、在Saprk中所有的算子可以分成两种:

        Transformation算子(转换算子):由一个RDD转化成另一个RDD,转换算子(懒执行)并不会自己执行,需要行为算子进行触发执行。

        Action算子(行为算子):可以出发Spark的Job,一个Action算子对应一个Job

3、Spark常用算子:

        1、转换算子(Transfromation算子):
                (1)、Map算子:

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

object Demo01Map {
  def main(args: Array[String]): Unit = {
    //获取spark的配置文件
    val conf: SparkConf = new SparkConf()
    //配置Spark任务的名称
    conf.setAppName("Demo01Map")
    //配置Spark的运行模式
    conf.setMaster("local")
    //搭建Spark的运行环境
    val sc: SparkContext = new SparkContext(conf)

    //读取文件数据
    val lineRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
//    lineRDD.foreach(println)

    /**
     * Map算子操作:与List是一致的,输入一条数据返回一条数据
     *需要接受一个函数:函数的参数类型:与RDD中每条数据数据类型保持一致=> 函数体:类型自定义
     */
    //需求:取出姓名以及班级
    val nameAndClazz: RDD[(String, String)] = lineRDD.map(kv => {
      val splits: Array[String] = kv.split(",")
      val name: String = splits(2)
      val calzz: String = splits(4)
      (name, calzz)
    })
    /**
     *foreach算子:行为算子,可以触发Spark的Job
     *与Map算子类似,区别是在与一个有返回值,另一个没有返回值
     */
    nameAndClazz.foreach(println)
  }

}
                (2)、FlatMap算子:

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

object Demo02flatMap {
  def main(args: Array[String]): Unit = {
    //获取Spark的配置文件
    val conf: SparkConf = new SparkConf()
    //设置Spark的任务名称
    conf.setAppName("Demo02flatMap")
    //设置Spark的运行模式
    conf.setMaster("local")
    //获取Spark的运行环境
    val sc: SparkContext = new SparkContext(conf)


    //读取数据文件
    val stuRDD: RDD[String] = sc.textFile("spark/data/words.txt")

    val words: RDD[String] = stuRDD.flatMap(kv => {
      kv.split(",")
    })
    words.foreach(println)
  }

}
                (3)、MapValues算子:


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

object Demo03MapValues {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo03MapValues")
    val sc: SparkContext = new SparkContext(conf)


    /**
     * mapValues:转换算子,首先需要构建一个kv格式的RDD,然后通过该算子对kv的v继续操作
     * 
     */

    //将学生的姓名和年龄取出来,然后将年龄减一
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    val nameAndAgeRDD: RDD[(String, Int)] = stuRDD.map(kv => {
      val splits: Array[String] = kv.split(",")
      val name: String = splits(1)
      val age: String = splits(2)
      val age1: Int = age.toInt
      (name,age1)
    })
    val nameAndRDD: RDD[(String, Int)] = nameAndAgeRDD.mapValues(kv => {
      kv - 1
    })
    nameAndRDD.foreach(println)
  }
}
               (4)、Filter算子:

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

object Demo04Filter {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setAppName("Demo04Filter")
    conf.setMaster("local")
    val sc: SparkContext = new SparkContext(conf)


    /**
     * filter:转换算子
     * 需要接收一个函数,函数的的参数类型与RDD中的每一条数据的类型一致=> 返回值类型是布尔类型的、
     * 如果返回是true,就保留数据,反之则过滤数据
     */

    //需求:过滤出年龄大于23的学生
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    val ageRDD: RDD[String] = stuRDD.filter(kv => {
      kv.split(",")(2).toInt > 23
    })
    ageRDD.foreach(println)
  }
}
                (5)、sortBy算子:

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

object Demo05SortBy {

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo05SortBy")
    val sc: SparkContext = new SparkContext(conf)


    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")

    /**
     * sortBy:转换算子,用于排序
     * 需要接收一个函数f用于指定按照什么进行排序
     * 默认是按照升序进行排序,可以通过ascending参数来控制升降序,false表示的是降序
     */
    val sortByRDD: RDD[String] = stuRDD.sortBy(c => {
      c.split(",")(2).toInt
    }, false)
    sortByRDD.foreach(println)



    //需求:先按照age升序,然后再按照id降序进行排序
    val ageAndIdRDD: RDD[String] = stuRDD.sortBy(k => {
      val age: String = k.split(",")(2)
      val id: String = k.split(",")(0)
      age + (999999999L - id.toInt).toString
    })
    ageAndIdRDD.foreach(println)
  }
}
                (6)、groupBy算子:

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

object Demo06GroupBy {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo06GroupBy")
    val sc: SparkContext = new SparkContext(conf)

    /**
     * groupBy:转换算子
     * groupBy:可以作用在任意给格式的的RDD上
     *groupBy:需要指定分组的条件,最后分完组之后,每一组数据是分组前的完整的每条数据,是将分组前的每一条数据完整的拉到对应的组中
     */
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    //需求:统计班级的人数
    val clazzRDD: RDD[String] = stuRDD.map(kv => {
      val clazz: String = kv.split(",")(4)
      clazz
    })
    val groupRDD: RDD[(String, Iterable[String])] = clazzRDD.groupBy(k => k)
    val lineRDD: RDD[(String, Int)] = groupRDD.map(kv => {
      val clazzName: String = kv._1
      val groupSize: Int = kv._2.size
      (clazzName, groupSize)
    })
    lineRDD.foreach(println)




    //使用groupBy求出每个班级的平均年龄
    stuRDD.map(kv=> {
      val clazz: String = kv.split(",")(4)
      val age: Int = kv.split(",")(2).toInt
      (clazz, age)
    })
      .groupBy(k=>{k._1})
      .map(kv=>s"${kv._1},${kv._2.map(kv1=>kv1._2).sum.toDouble /kv._2.size}")
      .foreach(println)
  }
}
               (7)、groupByKey算子:

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

object Demo07GroupByKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo07GroupByKey")
    val sc: SparkContext = new SparkContext(conf)


    /**
     * groupByKey:转换算子
     * groupByKey:不需要指定分组条件,会自动的默认按照key进行分组,得到的数据只由value组成
     * groupByKey:只能作用在kv形式的RDD
     * groupBYKey后续还可以继续进行聚合
     */


    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    val mapRDD: RDD[(String, Int)] = stuRDD.map(kv => {
      val clazz: String = kv.split(",")(4)
      (clazz, 1)
    })
    val keyRDD: RDD[(String, Iterable[Int])] = mapRDD.groupByKey()
    val countRDD: RDD[(String, Int)] = keyRDD.map(kv => {
      val clazz: String = kv._1
      val count: Int = kv._2.size
      (clazz, count)
    })
    countRDD.foreach(println)



    //统计班级的平均年龄
    stuRDD.map(kv=>{
      val age: Int = kv.split(",")(2).toInt
      val clazz: String = kv.split(",")(4)
      (clazz,age)
    })
      .groupByKey()
      .map(kv=>{
        val clazz: String = kv._1
        val avg: Double = kv._2.sum.toDouble / kv._2.size
        s"${clazz},${avg}"
      })
      .foreach(println)

  }
}

groupBy与groupKeyBy的区别:

        1、groupBy可以作用在任意形式的RDD,然而对于groupKeyBy来说,只能作用在kv形式的RDD

        2、groupBy在进行分组的时候需要指定分组的条件,但是groupKeyBy在分组的时候不需要指定分组条件,会自动根据key继续分组

        3、groupBy分组结束以后,组中的数据都是都是分组前的每一条完整的数据,对于groupKeyBy来说,分组结束后得到的数据是由kv形式的RDD中的value组成。

                    (8)、reduceByKey算子:


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

object Demo08ReduceByKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo08ReduceByKey")
    val sc: SparkContext = new SparkContext(conf)

    /**
     * reduceByKey:转换算子
     * 既可以按照key进行分组,又可以对value进行聚合,还可以在reduce之前也是就是在map阶段进行预聚合
     * 就是根据key进行分组聚合,然后再进行累加
     */
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    //统计班级的人数
    stuRDD.map(kv=>{
      val clazz: String = kv.split(",")(4)
      (clazz,1)
    })
      .reduceByKey((i1,i2)=>{
        i1+i2
      })
      .foreach(println)



    //统计班级的平均年龄
    /**
     * 对于预聚合是用于sum,count,max,min,avg(对于平均的这里不能使用)这五种
     */

    //解决方法:就是先求出人数和年龄总和,应为reduceByKey只使用在kv形式的RDD,所以将value设置成一个二元组用来存放年龄和人数
    stuRDD
      .map(kv=>{
        val clazz: String = kv.split(",")(4)
        val age: Int = kv.split(",")(2).toInt
        (clazz,(age,1))
      })
      .reduceByKey((i1,i2)=>{
        val ageSum: Int = i1._1+i2._1
        val count: Int = i1._2 + i2._2
        (ageSum,count)
      })
      .map(kv=>{
        val clazz: String = kv._1
        val avg: Double = kv._2._1.toDouble/kv._2._2
        (clazz,avg)
      })
      .foreach(println)
  }
}
                (9)、aggregateByKey算子:

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

object Demo09AggregateByKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo08ReduceByKey")
    val sc: SparkContext = new SparkContext(conf)


    /**
     * zeroValue:U 初始化的值,类型自定义
     * seqOp:(U,KV格式的RDD的V的类型)=>U 预聚合的操作
     * combOp:(U,U)=>U  聚合的操作
     *
     *(zeroValue:U )(seqOp:((U,KV格式的RDD的V的类型)=>U,combOp:(U,U)=>U))
     *
     * 同reduceByKey的区别在于:
     * 两者都能做预聚合,只不过在aggregateByKey中将聚合以及预聚合操作分开来了,可以单独定义
     * reduceByKey相当于聚合和预聚合操作共用相同的计算逻辑
     * 也只是用于kv形式的RDD
     * 当预聚合操作和聚合操作不一致时,可以使用aggregateByKey
     */

    //统计班级人数
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    stuRDD
      .map(kv => {
        val clazz: String = kv.split(",")(4)
        (clazz, 1)
      })
      .aggregateByKey(0)(seqOp = (z, v) => {
        z + v
      }, combOp = (i1, i2) => {
        i1 + i2
      })
      .foreach(println)

    //统计班级的平均年龄
    stuRDD.map(kv=>(kv.split(",")(4),kv.split(",")(2).toDouble))
      //因为求平均年龄是没有办法进行预聚合的,所以可以通过求出总数和个数然后间接的求出平均,
      // 所以我们需要求出两个参数,sum和count,应为需要接受两个参数,所以需要一个二元组来接受sum和count这两个变量
      /**
       * zeroValue:U 设置初始值,设置sum的初始值时0.0,double类型的,设置count的初始值是0类型是int类型的
       */
      .aggregateByKey((0.0,0))(seqOp = (u,age)=>{
        //预编译阶段,相当于是在map阶段的操作,取出第一个参数加上age,求出预编译阶段的sum(age),取出第二个参数,统计预编译阶段的人数
        (u._1+age,u._2+1)
    },combOp = (i1,i2)=>{
        //聚合阶段:此时的数据是某一个完成预编译map阶段的数据,年龄和人数
        (i1._1+i2._1,i2._2+i1._2)
      })
      .map(kv=>{
        s"${kv._1},${kv._2._1/kv._2._2}"
      })
      .foreach(println)
  }
}
        (10)、join算子、leftOutJoin算子、rightOutJoin算子、fullOutJoin算子

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

object Demo10Join {
  def main(args: Array[String]): Unit = {



    /**
     * join :转换算子,只能作用在kv形式的RDD中,并且表的关联使用的是key来做关联
     */

    //加载学生表以及学生的分数表合并
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo10Join")
    val sc: SparkContext = new SparkContext(conf)

    //读取学生的信息,将学生的学号设置成key,姓名设置成value
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    val stu1RDD: RDD[(String, String)] = stuRDD.map(kv => (kv.split(",")(0), kv))

    //读取学生的成绩表,将学号设置成key,分数设置成value
    val scoreRDD: RDD[String] = sc.textFile("spark/data/stu/score.txt")
    val score1RDD: RDD[(String, String)] = scoreRDD.map(kv => (kv.split(",")(0), kv))

    val joinRDD: RDD[(String, (String, String))] = stu1RDD.join(score1RDD)

    //(1500100488,(1500100488,丁鸿骞,22,男,理科四班,1500100488,1000008,99))
    // 需求:对于上面的数据,只保留id,姓名,age,clazz,科目,分数
    //此时要注意的是:key依旧是学生id,这个返回的value是一个二元组
    joinRDD.map(kv=>{
      val stuId: String = kv._1
      val stuName: String = kv._2._1.split(",")(1)
      val stuAge: String = kv._2._1.split(",")(2)
      val stuGender: String = kv._2._1.split(",")(3)
      val stuClazz: String = kv._2._1.split(",")(4)
      val subject: String = kv._2._2.split(",")(1)
      val score: String = kv._2._2.split(",")(2)
      (stuId,stuName,stuAge,stuClazz,stuGender,subject,score)
    })



    //使用左关联
    //(1500100843,(1500100843,管鸿达,21,男,文科五班,Some(1500100843,1000003,79)))
    //返回值类型:是一个二元组,后面的value返回的返回值也是一个二元组,保持左边的表完整性,当匹配的值没有的时候就返回None。
    val leftRDD: RDD[(String, (String, Option[String]))] = stu1RDD.leftOuterJoin(score1RDD)

    //对数据进行整理:取出姓名,和成绩,因为最后返回的类型是some,可以做模式匹配进行
    leftRDD.map(kv=>{
      val id: String = kv._1
      val name: String = kv._2._1.split(",")(1)

      val value: Any = kv._2._2 match {
        case Some(v) =>
          val score: String = v.split(",")(2)
          s"${score}"
        case None =>
          "None"
      }
//val list: List[String] = kv._2._2.toList
//      val score1: List[String] = list.map(kv => {
//        val score: String = kv.split(",")(2)
//        score
//      })
      s"${id},${name},${value}"
    })
      //使用右关联:(1500100430,(Some(1500100430,屈佑运,22,男,文科五班),1500100430,1000006,32))
    //保持右边的表的完整性,如果没有匹配到返回的是None

    val rightRDD: RDD[(String, (Option[String], String))] = stu1RDD.rightOuterJoin(score1RDD)
    rightRDD.map(kv => {
      val id: String = kv._1
      val scoSplits: Array[String] = kv._2._2.split(",")
      val stuStr: String = kv._2._1 match {
        case Some(v) =>
          val stuSplits: Array[String] = v.split(",")
          s"${stuSplits(1)},${stuSplits(4)}"
        case None =>
          "None"
      }

      s"$id,$stuStr,${scoSplits(1)},${scoSplits(2)}"
    })



    //全外连接:
    val fullRDD: RDD[(String, (Option[String], Option[String]))] = stu1RDD.fullOuterJoin(score1RDD)
    fullRDD.map(kv => {
        val id: String = kv._1
        val stuStr: String = kv._2._1 match {
          case Some(v) =>
            val stuSplits: Array[String] = v.split(",")
            s"${stuSplits(1)},${stuSplits(4)}"
          case None =>
            "None"
        }

        val scoStr: String = kv._2._2 match {
          case Some(v) =>
            val scoSplits: Array[String] = v.split(",")
            s"${scoSplits(1)},${scoSplits(2)}"
          case None =>
            "None"
        }

        s"$id,$stuStr,$scoStr"
      })
      .foreach(println)









  }

}
        (11)、  Sample算子:

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

object Demo11Sample {
  def main(args: Array[String]): Unit = {


    /**
     * 随机放回:Sample,转换算子,需要传入三个参数
     * withReplacement:是否放回,不放回的目的是保持数据有重复
     * fraction:取样比例  ,由于是随机放回,所以是只能每次只能大致的接近这个比例
     * seed:种子,随机数种子,如果需要每次的输入的数目是一致的,那么对于种子seed来说,需要给定一个固定值
     */

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo11Sample")
    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    stuRDD.sample(withReplacement = false,fraction = 0.01,100).foreach(println)
  }

}
        (12)、Union算子:
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Demo12Union {
  /**
   * union:转换算子,
   * 只有两个结构类型一致的数据才能进行union操作
   * 对RDD中数据,合并之后是不会进行去重的,如果需要去重,就需要进行distinct
   * 对于union过后的RDD分区的数目是两个RDD分区数目的总和
   */
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo12Union ")
    val sc: SparkContext = new SparkContext(conf)

    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    val line1: RDD[String] = stuRDD.sample(false, 0.01, 111)
    val line2: RDD[String] = stuRDD.sample(false, 0.01, 100)

    val unionRDD: RDD[String] = line1.union(line2)
    unionRDD.foreach(println)
    //对合并之后的union进行合并
    unionRDD.distinct().foreach(println)
  }

}
        (13)、Cartesian算子:
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Demo13Cartesian {

  /**
   *Cartesian:转换算子,做笛卡尔积:指的是不指定条件或者是条件恒成立的关联
   */
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setAppName("Demo13Cartesian")
    //这里指定的并行度
    conf.setMaster("local")
    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    val rdd01: RDD[String] = stuRDD.sample(false, 0.01, 100)
    val rdd02: RDD[(String, String)] = rdd01.cartesian(rdd01)
    rdd02.foreach(println)
  }

}
         (14)、mapPartition算子:



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

import java.sql.{Connection, DriverManager, PreparedStatement, ResultSet}

object Demo14MapPartitions {
  def main(args: Array[String]): Unit = {

    // 假设有一个黑名单:List[String]
    val blacklist: List[String] = List[String]("1500100001", "1500100011", "1500100111")

    /**
     * 基于黑名单结合SparkCore,从MySQL中获取学生的基本信息
     */

    // 除了使用textFile加载数据构建RDD,还可以通过Scala本地集合来构建RDD
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo14MapPartitions")

    val sc: SparkContext = new SparkContext(conf)

    val blacklistRDD: RDD[String] = sc.parallelize(blacklist)

    //    blacklistRDD.foreach(println)

    // conn、pSt的构建不能放在map算子外面,不然就会导致Task未被序列化的问题
    // Task为什么需要被序列化?因为Task最终是需要由Driver端进行发送到Executor中执行,
    // Driver端和Executor之间交互是通过网络进行的,所以Task必须序列化后才能进行网络传输

    /**
     * 虽然Spark的代码都在一个main方法里,但实际上运行的时候会分为两个部分:
     * 算子内部的代码:最终会变成Task到Executor中执行
     * 算子外部的代码:在Driver端执行
     */


    // 使用mapPartitions算子进行优化:可以单独对每个分区的数据进行操作
    //当不使用mapPartitions、foreachPartition的时候,在每一次与外界交互的时候,每读取一次数据,就需要与外界进行一次来连接,效率不高,
    // 对于mapPartitions、foreachPartition的作用:因为每一个Task是作用在每一个分区上,然后每一个分区中会有很多的数据,
    // 此时mapPartitions、foreachPartition就可以作用在每一个分区,在执行task任务的时候,每一个分区都只需要与外界建立一次连接,就不要每读取一条数据就与外界建立一次连接。
    /**
     * 如果需要在Spark代码中同外部的某个数据源建立连接,是作用在Spark上的每一个分区上
     * 连接建立之后如果是需要从外部获取数据一般就是使用 mapPartitions  转换算子
     * 如果是需要将结果数据写入外部系统,则一般使用 foreachPartition 行为算子
     */

    blacklistRDD
      // 对每一条数据进行操作
      .map(id => {

        /**
         * 首先conn、pSt都是不可以放在算子外部的,因为它们不能够被序列化
         * 但是放算子里又会出现连接被重复创建的问题,相当于一条数据就需要建立一次连接
         */
        // 建立JDBC连接
        val conn: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")

        // 创建prepareStatement
        val pSt: PreparedStatement = conn.prepareStatement("select name,age,gender,clazz from students where id = ?")

        pSt.setString(1, id)
        val rs: ResultSet = pSt.executeQuery()
        var returnStr: String = ""
        while (rs.next()) {
          val name: String = rs.getString("name")
          val age: Int = rs.getInt("age")
          val gender: String = rs.getString("gender")
          val clazz: String = rs.getString("clazz")
          returnStr = s"$id,$name,$age,$gender,$clazz"
        }
        returnStr
      })
    //      .foreach(println)

    // 统计班级里存在黑名单学生的人数

    blacklistRDD
      .mapPartitions(iter => {

        // 建立JDBC连接
        val conn: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")

        // 创建prepareStatement
        val pSt: PreparedStatement = conn.prepareStatement("select name,age,gender,clazz from students where id = ?")

        // iter表示RDD中一个分区的数据
        iter
          // 相当于对分区里的每一条数据进行操作,
          // 这里的map只是迭代器提供的一个普通方法而已,并不会有内部外部之分
          .map(id => {
            pSt.setString(1, id)
            val rs: ResultSet = pSt.executeQuery()
            var returnStr: String = ""
            while (rs.next()) {
              val name: String = rs.getString("name")
              val age: Int = rs.getInt("age")
              val gender: String = rs.getString("gender")
              val clazz: String = rs.getString("clazz")
              returnStr = s"$id,$name,$age,$gender,$clazz"
            }
            returnStr
          })
      })
      .foreach(println)

    blacklistRDD
      .repartition(2) // 对RDD进行重新分区,指定RDD的分区数
      .mapPartitionsWithIndex((index, iter) => {
        println(s"正在处理的分区index为:$index")
        iter
      }).foreach(println)


  }

}
        (15)、ForeachPartition算子:


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

import java.sql.{Connection, DriverManager, PreparedStatement}

object Demo15ForeachPartition {
  def main(args: Array[String]): Unit = {
    // 统计班级人数,将最终的结果写入MySQL

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo15ForeachPartition")

    val sc: SparkContext = new SparkContext(conf)

    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")

    val clazzCntRDD: RDD[(String, Int)] = stuRDD.map(line => (line.split(",")(4), 1))
      .reduceByKey(_ + _)


    //    clazzCntRDD
    //      .foreach(kv => {
    //        /**
    //         * 同map算子类似,无法将连接建立在算子外部,否则会出现Task不能够被序列化的问题
    //         * 同样的放到算子内部则性能太低了,每一条数据会建立一次连接
    //         */
    //        // 建立JDBC连接
    //        val conn: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student?characterEncoding=utf8", "root", "123456")
    //        // 创建prepareStatement
    //        val pSt: PreparedStatement = conn.prepareStatement("insert into clazz_cnt (clazz,cnt) values (?,?)")
    //
    //
    //        val clazz: String = kv._1
    //        val cnt: Int = kv._2
    //
    //        // 设置参数
    //        pSt.setString(1, clazz)
    //        pSt.setInt(2, cnt)
    //
    //        // 执行插入语句
    //        pSt.execute()
    //
    //      })

    clazzCntRDD
      // 同mapPartitions类似 适用于将结果写到外部系统
      .foreachPartition(iter => {
        // 建立JDBC连接
        val conn: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student?characterEncoding=utf8", "root", "123456")
        // 创建prepareStatement
        val pSt: PreparedStatement = conn.prepareStatement("insert into clazz_cnt (clazz,cnt) values (?,?)")

        var c: Int = 0
        val batchSize: Int = 1000
        // 处理每个分区内部的每一条数据
        iter
          .foreach(kv => {
            c += 1
            val clazz: String = kv._1
            val cnt: Int = kv._2

            // 设置参数
            pSt.setString(1, clazz)
            pSt.setInt(2, cnt)

            // 执行插入语句
            //            pSt.execute()
            pSt.addBatch() // 将构建好的insert语句添加到Batch中,等待批量执行

            if (c >= batchSize) {
              // 执行批量插入
              //优化:数据进行批量写入
              pSt.executeBatch()
              c = 0
            }
          })
        if (c > 0) {
          // 执行批量插入
          pSt.executeBatch()
        }
      })
  }

}
(16)、CoGroup算子:

object Demo16CoGroup {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo16CoGroup")
    val sc: SparkContext = new SparkContext(conf)

    /**
     * CoGroup:转换算子,功能类似于fullOutputJoin,也是作用在kv的格式上面
     * parallelize:作用就是将list转化成RDD
     */

    val kvRDD01: RDD[(String, Int)] = sc.parallelize(List[(String, Int)]("kv" -> 1, "kv1" -> 2, "kv2" -> 3))
    val kvRDD02: RDD[(String, Int)] = sc.parallelize(List[(String, Int)]("kv" -> 1, "kv1" -> 2, "kv2" -> 3))

    kvRDD01.cogroup(kvRDD02).foreach(println)
  }

}
2、行为算子(Action算子):
                1、foreach算子:行为算子,可以出发Spark的Job任务,与Map算子类似,区别是在于一个是有返回值,另一个是没有返回值
    2、主要包括:foreach,foreachPartition,collect,collectAsMap,reduce,reduceByKeyLocally,count,top,lookup


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

object Demo17Action {
  def main(args: Array[String]): Unit = {
    /**
     * 行为算子(Action算子):指的是没有返回值或者是返回值的类型不是RDD
     * 主要包括:foreach,foreachPartition,collect,collectAsMap,reduce,reduceByKeyLocally,count,top,lookup
     */

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo17Action")
    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")

    //1、collect:作用是将RDD中的所有的数据转化成本地集合Array数组,数据中的泛型与RDD中的里类型保持一致
    val strings: Array[String] = stuRDD.collect()
    //此处的println并不是spark中的算子,所以触发job任务的并不是println,是collect触发的job任务
    strings.foreach(println)



    //take:作用取出前n条数据,转换成scala中的本地集合
    val strings1: Array[String] = stuRDD.take(2)
    strings1.foreach(println)


    //reduce:作用是对全局的数据进行聚合操作
    //需求:求出所有学生年龄的总和

    val ageSum: Int = stuRDD.map { kv => {
      val age: Int = kv.split(",")(2).toInt
      age
    }
    }
      .reduce((i1, i2) => {
        i1 + i2
      })
    println(ageSum)

    //reduceByKeyLocally:是只能作用在kv形式的RDD中,作用在作用对数据进行聚合,并且将最终的结果转化成本地的map集合
    //需求:统计每个班级的人数
    val clazzRDD: RDD[(String, Int)] = stuRDD.map(kv => (kv.split(",")(4), 1))
    clazzRDD.reduceByKeyLocally((i1,i2)=>{
      i1+i2
    })
      .foreach(println)//这里的foreach是本地的map集合所提供的


    //count:返回的是数量,返回值的类型是long
    val l: Long = stuRDD.count()
    println(l)



    //top:取出前n条数据,中间是有排序的问题。
    val topRDD: Array[String] = stuRDD.top(10)
    topRDD.foreach(println)



    //lookUp:只能作用在kv形式的RDD上面,作用是根据key来找出对应的value的值,返回值的类型是sep,有序的
    val lookUpRDD: RDD[(String, String)] = stuRDD.map(kv => (kv.split(",")(0), kv))
    val lookUp01: Seq[String] = lookUpRDD.lookup("1500100007")
    lookUp01.foreach(println)
  }

}
 
 

你可能感兴趣的:(Spark,spark,大数据,分布式)