Spark之RDD学习笔记

package com.atguigu.Spark.RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

/**
  * Create by zgn on 2021/12/1 18:11
  *
  **/

object RDD_Instance_Memory {
  def main1(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    //    自己写的配置的优先级大于环境
    conf.set("spark.default.parallelism", "4") //设置线程数并行数
    val sc = new SparkContext(conf)
    //   TODO  从内存(集合)中创建RDD
    //    parallelize( 平行放置;使……平行于……;使程序(适合)进行计算)方法用于构建rdd
    //    也可以将这个集合当成数据模型处理的数据源

    val Rdd = sc.parallelize( //并行
      Seq(1, 2, 3, 4)
    )
    //    使用下面的方法创建更加清晰 见名知义
    val rdd1 = sc.makeRDD(// 底层调用了 parallelize(seq, numSlices)
      Seq(1, 2, 3, 4)
    )
    //    TODO totalCores 当前Master总的核数
  }

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("RDD")
    conf.set("spark.default.parallelism","4")//设置线程的并行数
    val sc = new SparkContext(conf)
    /*TODO 从内存集合中创建RDD, parallelize方法用于构建RDD平行放置 也可以将这个集合当成数据模型处理
    * 的数据源*/
    val Rdd: RDD[Int] = sc.parallelize(Seq(1,2,3,4,5,6))
    /*TODO 使用下面的方法创建更加清晰*/
    val rdd1: RDD[Int] = sc.makeRDD(Seq(12,3,4,5,6))
    /*TODO  totalCores 当前master总的核数*/
  }
}

object RDD_Instance_Memory1 {
  def main1(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    val rdd:RDD[Int] = sc.parallelize(
    //      List(1, 2, 3, 4)
    //    )
    val rdd = sc.makeRDD(
      List(1, 2, 3, 4)
    )
    println(rdd)
  }

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,3,4,5,6))
    /*Return an array that contains all of the elements in this RDD.
    * collect中可以传入一个偏函数*/
    rdd.collect().foreach(println)
  }
}

object RDD_Instance_File {
  def main1(args: Array[String]): Unit = {
    //    TODO 从文件中创建RDD
    //    textFile方法可以通过路径获取数据 所以可以将文件作为数据处理的 数据源
    //    textFile的路径可以是相对路径 也可以是绝对路径  也可以是HDFS路径
    //    textFile的路径不仅可以是文件的路径 也可以是目录的路径 还可以是通配符路径
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    val rdd :RDD[String] = sc.textFile("text*.txt")
    //    TODO  如果想要在读取到文件的内容时直到文件的来源
    //              文件路径   文件内容
    val rdd: RDD[(String, String)] = sc.wholeTextFiles("text*.txt")
    //    TODO  saveAsTextFile方法可以生成分区文件  构建RDD的时候 如果没有指定数据分区的数量
    //    那么会使用默认的分区数量 makeRDD方法存在第二个参数 这个参数表示分区数量 (存在默认值)
    // 分区的好处可以达到负载均衡和并行消费的能力
    rdd.saveAsTextFile("output")
    rdd.collect().foreach(println)
  }
  def main(args: Array[String]): Unit = {
    /*TODO 从文件中创建RDD textFile可以通过路径获取数据 可以将文件作为数据处理的数据源
    * textFile可以是相对路径,可以是绝对路径,也可以是HDFS路径
    * textFile路径不仅可以是文件的路径 也可以是目录的路径 还可以是通配符的路径*/
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc: SparkContext = new SparkContext(conf)
    /* * This method is identical to `parallelize`.
   * @param seq Scala collection to distribute
   * @param numSlices number of partitions to divide the collection into*/
    sc.makeRDD(List(1,3,4,5))
    sc.parallelize(List(12,3,4,5))
    /*TODO 如果想要在读取到文件的内容时知道文件的来源*/
    val rdd: RDD[(String, String)] = sc.wholeTextFiles("text*.txt")
    /*TODO saveAsTextFile 方法可以生成分区文件 构建RDD的时候 如果没有指定数据分区的数量
    * 那么会使用默认的分区数量 makeRDD方法存在第二个参数 这个参数表示分区数量 存在默认值
    * 分区的好处是可以达到负载均衡和并行消费的能力*/
    rdd.saveAsTextFile("output_1")
    rdd.collect().foreach(println)
  }
}

object RDD_Instance_File1 {
  def main1(args: Array[String]): Unit = {
    //    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    //    val sc = new SparkContext(conf)
    //    val rdd = sc.wholeTextFiles("text.txt")
    //    rdd.saveAsTextFile("output1")
    //    rdd.collect.foreach(println)
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(
      List(1, 2, 3, 4, 5), 3 //3 个分区
      //      1  , 2 3  , 4 5
    )
    //    TODO 回忆  kafka 生产者分区策略 如果指定了分区号 直接将该值作为分区值
    //    没有指明分区但是指明了key  将key的hash值 和topic的分区数取模得到分区值
    //    没有指明分区也没有指明key  默认采用粘性分区
    //    TODO RDD的分区
    rdd.saveAsTextFile("output5")
    println(rdd)
    rdd.foreach(println)
  }

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6),3)
    /*TODO kafka生产者分区分配策略 如果指定了分区号 直接将该值作为分区值*/
    /*没有指明分区但是指明了key 按key的hash值 和topic的分区数量取模得到分区值
    * 没有指明分区也没有指明key 默认采用粘性分区*/
    rdd.saveAsTextFile("output_5")
    rdd.foreach(println)
  }
}

object RDD_TextFile {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO  从文件中创建RDD
    //    textFile方法可以在读取文件的时候 设定分区
    //    设定分区的时候 应该传递第二个参数  如果不设定 存在默认值
    //    第二个参数表示的是最小分区数 所以最终的分区数量可以大于这个值的
    //    TODO  spark 读取文件底层其实就是hadoop读取文件
    //    spark分区数量其实就来自于 hadoop读取文件的切片
    //    numSplits 想要的切片数量2    totalSize 文件总大小 20     goalSize =>
    //    预计每个分区的字节大小
    //totalSize/numSplits = 10 每个分区放10个字节
    //    一共20个字节 需要两个分区  每个分区10个字节 真tm的对
    //    如果有剩余的自己 要判断剩余的字节和每个分区的字节数相比,如果大于1.1则
    //    再增加一个分区 否则不增加
    val rdd = sc.textFile("text.txt", 3) //如果这里是3 字节数totalSize是20
    //    每个分区放6个字节  一共20个字节 3个分区 剩2个字节  2/6>0.1  4个分区
    //    TODO 数据是怎样分配到不同的分区的
    //    TODO  分区数据的处理也是由Hadoop决定的
    //    TODO hadoop 在计算分区时和处理数据的逻辑是不一样 处理数据按行读  分区则是按字节放
    //    TODO Spark读取文件数据底层使用的就是Hadoop读取的 所以读取规则用的是hadoop
    /* hadoop读取数据是按行读的。不是按字节读的
    * hadoop读取数据是按偏移量读取的 不会重复读取相同的偏移量
    * 数据   偏移量
    * 1@@    012
    * 2@@   345
    * 3      6
    * 计算读取偏移量  3个分区  7个字节
    * [0,3]     12
    * [3,6]     3
    * [6,7]    空
    * 数据是怎么读到各个分区的  计算分区读取的偏移量   不重复读偏移量 */
    rdd.saveAsTextFile("output4")
    //    TODO 早起回顾
    //    Driver =>Task (调度) =>exector(执行) 如果将数据拉取到其他的节点进行计算 称之为移动数据
    //    将任务发送给不同的节点进行计算 称之为移动计算
    //    TODO  数据和计算 在一起 怎么放置在一起 这个操作涉及到一个本地化的概念
    //    数据和计算在同一个进程中 进程本地化   在同一个节点中  节点本地化  不同的任意节点 任意
    //    数据和计算在同一机架 机架本地化
    //    TODO RDD 执行原理  计算资源 和计算模型  Spark on Yarn
    //
  }
}

object RDD_Instance_Dist {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    Textfile()文件的路径可以是绝对路径 可以是相对路径
    //    绝对路径:不可改变的路径
    //    相对路径:可以改变的路径  相对当前的路径
    /* 网络   网络协议  ip地址 端口号
    * 本机   file:///
    * 默认情况下 相对路径的基准路径为IDEA项目的根路径 不是模块的根路径
    * textFile 读取的文件可以是文件 也可以是目录
    * 可以用正则表达式 匹配文件名读取文件
    * 如果想要获取文件内容是来自于哪一个文件 需要采用方法 wholeTextFiles*/

    //    val rdd = sc.textFile("text.txt") 这个方法第一个参数表示文件路径 第二个参数表示最小分区数
    //    参数存在默认值 是不需要传递的 默认值 min(defaultParallelism,2)
    //    spark的文件操作自己是没有的   底层采用的是hadoop的文件操作
    //    totalSize:文件总大小 总字节    goalSize:每个分区期望的字节数
    //    分区数量 = totalSize / goalSize
    /*  阈值1.1  剩余字节数/ 切片大小 也就是每个分区的字节数 如果大于 0.1 会增加一个分区/切片
    *  TODO Spark 数据是怎么分配到分区的
    *  hadoop 计算分区和读取分区数据方式不一样
    *  计算分区是按照字节来计算的  读取分区数据不是按照字节  按照行来读取的
    *   读取数据的时候 按照偏移量来计算  相同的偏移量不能重复读取
    *   每个分区的偏移量
    *   1@@  012
    *   2@@   345
    *   3   6
    *   [0-3]  12
    *   [3-6]   3
    *   [6-7]  空
    *  RDD的弹性可以体现在分区的变化 默认情况下 分区数据是不均衡的 可以在任务的执行过程中 改变分区
    *  让数据更加均衡
    *
    *   */
    val rdd = sc.wholeTextFiles("text.txt")
    rdd.saveAsTextFile("output11")
    /*TODO RDD 转换算子
    * 从认知心理学的角度来理解 解决问题的方式其实就是 改变问题的状态
    * 问题(初始状态)   问题(审批中)    问题(解决)
    * 这里的Operator翻译过来就是【算子】 Operator可以理解为 操作 方法 功能
    * 算子 : 可以理解为方法
    * 取名为算子的原因 : 就是为了和方法区分开 尤其是scala集合的方法
    * TODO RDD 的方法称之为 算子 scala集合的方法就是方法/函数
    * 转换: 将A 变成B(映射)
    * 所谓的转化算子 其实就是转换方法  =>  通过方法 将旧的RDD转换成B(新的RDD)
    * 旧的RDD  = 算子 > 新的RDD  目的: 拓展功能  将不同的RDD 功能组合在一起
    * RDD 使用的基本方式
    *
    * RDD 转换算子 :value类型    双value类型      key-value类型   */
  }
}

object RDD_oper_transform {
  def main1(args: Array[String]): Unit = {
    //    TODO  算子 - 转换算子
    /**/
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO RDD转换后 分区的变化 :默认执行算子后  1 产生的新的RDD的分区数量不变
    //    2 数据处理完后 所在分区不变
    val rdd = sc.makeRDD(List(1, 2, 3, 4))
    //    TODO map  转换映射
    //    map算子 可以将数据集中的每一条数据转换 映射后 返回新的数据 形成新的数据集合模型
    //    旧的RDD  =算子 map > 新的RDD   新的RDD包含新的额RDD的功能 将旧的RDD 转换成新的RDD
    //    并不意味着 转换成新的RDD
    val newrdd = rdd.map(_ * 2)
    newrdd.saveAsTextFile("outputnew")
    newrdd.collect().foreach(println)
    sc.stop()
  }

  def main2(args: Array[String]): Unit = {
    /*TODO 转换算子
    * RDD转换后,分区的变化,默认执行算子后 1 产生新的RDD的分区数量不变 2 数据处理完后 所在的分区不变*/
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    val newRDD: RDD[Int] = rdd.map(_*3)
    newRDD.saveAsTextFile("output_new")
    newRDD.collect().foreach(println)
    sc.stop()
  }

  def main(args: Array[String]): Unit = {
    //    TODO 并行的概念  数据的执行顺序
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4))
    //    TODO 因为spark的计算是并行分布式计算 那么不同分区的数据没有顺序
    //    但是同一个分区的数据是有序的 分区间是无序的
    //    TODO  如果conf的配置 是local 单核的 map算子的执行就是串行的
    //    如果是local* 就是并行的
    rdd.map(
      num => {
        println("num" + num)
        num * 2
      }
    )
    sc.stop()
  }
}

object Spark_Exercise_Transform {
  def main1(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 - 转换 -map
    //    从服务器日志Apache.log中获取用户请求URL资源路径
    val lineRDD = sc.textFile("Apache.log")
    //flatMap() => 扁平化 => 取得的是原来数据拆分的整体 但是我们只要URL
    //所以我们这里用的是 map map可以格式转化后取得部分数据
    val urlRDD = lineRDD.map(
      line => {
        val arrayline = line.split(" ")
        arrayline(6)
      }
    )
    println(urlRDD)
  }

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
//    算子-转换
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5))
    val rdd1: RDD[Int] = rdd.mapPartitions(
      list => {
        println("*********")
        list.map(_ * 2)
      }
    )
    rdd1.collect().foreach(println)
    sc.stop()

  }
  def main2(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO  算子 - 转换
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    val rdd1 = rdd.mapPartitions(//拿到一个分区的所有的数据
      list => {//每一个分区都会走一遍该逻辑
        println("****************") //这个方法会走两遍 因为有两个分区
        list.map(_ * 2)
      }
    )
    rdd1.collect().foreach(println)
    sc.stop()
    //    TODO 装饰者设计模式:其实就是一个完整的逻辑被切分成不同的组合模块
    //    数据集中的每一条数据必须全部处理完毕 才会执行下一条逻辑
  }
}

object RDD_oper_Transform {
  def main1(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //    可以以分区为单位进行数据的处理 处理的逻辑会调用分区数量的次数
    //    既然以分区为单位 所以处理的数据 就是分区的数据集合 处理后 返回的也应改是数据集合
    //    TODO mapPartitions的性能比map会高一些
    //    主要体现在资源的损耗上 mappartitions的资源损耗以分区为单位  map以单个数据为单位
    //    从数据处理的角度 两者区别 不大
    //    mappartitions在处理数据的时候 会依托当前的内存 所以内存不足的情况 不推荐使用
    //    TODO  map在处理完数据后 数据会被释放 所以内存占用不会很多 推荐使用
    //    mappartitions从使用的角度来讲 传递一个迭代器 返回一个迭代器  所以无需考虑数据的个数
    //    map从使用的角度来讲 传递一个数据 返回一个数据 数据不能丢失  不能变多也不能变少
    val rdd1 = rdd.mapPartitions(
      iter => {
        println("********")
        iter
      }
    )
    rdd1.collect()
    sc.stop()
  }

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),2)
    val rdd1: RDD[Int] = rdd.mapPartitions(
      iter => {
        println("********")
        iter //传递一个迭代器 返回一个迭代器 迭代器中数据的个数
      }
    )
    rdd1.collect().foreach(println)
    sc.stop()
  }
}

object Test_Oper_Max_partition {
  def main1(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //    TODO  获取每个分区的最大值
    //    map只考虑数据本身 而不考虑所在的分区
    //    TODO mappartition要求返回的数据的迭代器 而不是某个值
    //   val rdd1 = rdd.mapPartitions(
    //      iter =>{
    //        List(iter.max).iterator
    //      }
    //    )
    //    TODO 获取指定分区的最大值
    //    TODO Spark提供了一个算子 可以获取分区 数据的同时 获取分区编号
    //
    val rdd1 = rdd.mapPartitionsWithIndex(
      (index, iter) => {
        if (index == 1) {
          List(iter.max).iterator
        } else {
          Nil.iterator
        }
      }
    )
    rdd1.collect().foreach(println)
    sc.stop()
  }

  def main4(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),2)
    val rdd1: RDD[Int] = rdd.mapPartitionsWithIndex(
      (index, iter) => {
        if (index == 1) {//对每一个分区的数据进行处理 以分区为单位
          iter
        } else {
          Nil.iterator
        }
      }
    )
    rdd1.collect().foreach(println)
    sc.stop()
  }

  def main2(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子 - 扁平映射
    val rdd = sc.makeRDD(List(List(1, 2), List(3, 4)))
    //    TODO  获取每个分区的最大值
    //    TODO  flatmap :扁平化 将整体拆分成个体数据 将1个变成N个
    //    flatmap 参数要求 :传递一个值 返回一个可迭代的集合
    //    概念(原理)和应用不一样
    val newRDD = rdd.flatMap(
      list => list
    )
    newRDD.collect().foreach(println)
    sc.stop()
  }

  def main6(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(List(1,2),List(3,4)))
    rdd.flatMap(
      list => list
    )
    rdd.collect().foreach(println)
    sc.stop()
  }

  def main3(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子 - 扁平映射
    val rdd = sc.makeRDD(List(List(1, 2), 5, List(3, 4)))
    //    TODO  获取每个分区的最大值
    //    TODO  flatmap :扁平化 将整体拆分成个体数据 将1个变成N个
    //    flatmap 参数要求 :传递一个值 返回一个可迭代的集合
    //    概念(原理)和应用不一样
    //    val newRDD = rdd.flatMap(
    //    case i : List[_] =>{
    //      i
    //    }
    //    case other : Int => {
    //      List(other)
    //    }
    //    )
    //    newRDD.collect().foreach(println)
    sc.stop()
  }

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子 - glom
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //    TODO glom 算子可以将一个分区的数据组合成一个整体使用 可以调用集合的很多功能
    //    TODO 问题在于处理的时候 会将一个分区数据全部加载到内存  形成集合 那么内存可能不足
    /* Return an RDD created by coalescing all elements within each partition into an array.*/
    val rdd1 = rdd.glom()//返回元素是array的rdd 一个分区的所有元素组成了一个array
    val max = rdd1.map(
     array => array.max
    )
    println(max.sum())
  }

  def main7(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子 - 数据的分区
    //    一条数据的处理 肯定要落盘 Shuffle的操作会将整个数据的流程一分为二 分为两个阶段
    //    一个阶段将数据 用于将数据写入磁盘 全部写完后 另一个阶段 用于读取磁盘中的数据
    //    shuffle会将数据打乱重新组合 可能会导致数据的不均衡
    //    如果数据不均衡 会导致资源的浪费 可以尝试改变分区解决
    //
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //    TODO 分组将数据根据指定的规则 增加标记  相同标记的数据放在一起
    //    分组后 数据类型为 kv元类型 其中k是分组的标记  v就是相同标记的可迭代集合
    val rddgroup = rdd.groupBy(_ % 2)
    rddgroup.collect().foreach(println)
    sc.stop()
  }

  def main8(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子 - GroupBy
    val rdd = sc.makeRDD(List("hello", "nihao"), 2)
    val rddgroup = rdd.groupBy(_.charAt(0))//k 为分组的标记  v 为 iterable可迭代的集合
    rddgroup.collect().foreach(println)
  }

  def main9(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 -转换算子 - GroupBy
    //    groupBy算子 根据函数计算结果进行分组
    //    groupBy算子执行结果 为KV数据类型   k为分组的表示  v为同一个组的数据可迭代集合
    //    TODO 默认情况下 数据处理后 所在的分区不会发生改变
    //    spark要求 一个组的数据必须在一个分区中
    //    同一分区的数据被重新分配  称为Shuffle
    //    TODO shuffle会将计算过程一分为二  形成两个阶段 一个阶段用于写数据 一个阶段用于读数据
    //    写数据的阶段如果没有完成 读数据的阶段是不能执行的
    //    shuffle操作可以更改分区
    val rdd = sc.textFile("Apache.log")
    val timeToOne = rdd.map(
      line => {
        val datas = line.split(" ")
        val time = datas(3)
        (time.split(":")(1), 1)
      }
    )
    val groupRDD = timeToOne.groupBy(_._1)
    val result = groupRDD.mapValues(_.size)
    result.collect().foreach(println)
    sc.stop()
  }
}

object Little_Test {
  def main1(args: Array[String]): Unit = {
    //    TODO 算子 - 转换  根据单词的首个字母进行分组
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    conf.set("spark.local.dir", "e:/test")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(
      List("Hello", "hive", "hbase", "Hadoop")
    )
    val rddgroup = rdd.groupBy(_.substring(0, 1).toUpperCase()) //不区分大小写
    rddgroup.collect().foreach(println)
    sc.stop()
  }

  //  TODO 从服务器日志中统计Apache.log中获取每个时间段访问量
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    conf.set("spark.local.dir", "e:/test")
    val sc = new SparkContext(conf)
    val rddlines = sc.textFile("Apache.log")
    val groupRDD = rddlines.map(
      line => {
        val arrayline = line.split(" ")
        val time = arrayline(3)
        val timearray = time.split(":")
        (timearray(1), 1)
      }
    ).groupBy(_._1) //TODO  groupBy这个算子 可以实现Wordcount (1/10) v是可迭代的集合
    val timeCount = groupRDD.mapValues(_.size)
    timeCount.collect().foreach(println)
  }
}

object filter_Test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子 转换 filter 过滤
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6))
    //    TODO  filter可以按照指定的规则对每一条数据进行过滤筛选 根据指定的规则进行筛选过滤 符合规则的
    //    TODO 数据保留 不符合规则的数据丢弃 当数据进行筛选过滤后 分区默认不变 但分区的数据可能不均衡
    //    TODO 出现数据倾斜 比如两个分区 个1000条数据 过滤后一个分区就剩1条 另一个分区剩999条
    //    数据处理结果为true 表示数据保留 相反 如果为false 表示数据丢弃
    /*Return a new RDD containing only the elements that satisfy a predicate*/
    val filterRDD = rdd.filter(
      num => {
        num % 2 == 1 //过滤出奇数 ==1返回true 保留
      }
    )
    filterRDD.collect().foreach(println)
  }
}

object little_Test_filter {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 算子  filter 从服务器日志数据Apache.log中获取2015年5月17日的请求路径
    val rddline = sc.textFile("Apache.log")
    val filterLine = rddline.filter(
      line => {
        val lineArray = line.split(" ")
        val time = lineArray(3)
        time.startsWith("17/05/2015") //字符串以XXX开头 返回的是true则保留 否则丢弃
      }
    )
    filterLine.collect().foreach(println) //过滤的结果是完整的行
    val r = filterLine.map(
      line => {
        val lineArray = line.split(" ")
        lineArray(6)
      }
    )
    r.collect().foreach(println)
    //    TODO 解决数据倾斜? 扩容 hashmap 极限情况下放11条数据形成红黑二叉树
    //    放第九个的时候 16扩充32  放第十个的时候 32 扩充到64 第11个的时候调整结构形成红黑二叉树
    //  数据抽样  推算
  }
}

object filter_Test_1 {
  def main1(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO filter filter 算子可以将指定的数据进行过滤 满足的数据保留 不满足的丢弃
    //
    val lineRDD = sc.textFile("Apache.log")
    val rdd1 = lineRDD.filter(
      line => {
        val arrayLine = line.split(" ")
        val time = arrayLine(3)
        time.startsWith("17/05/2015")
      }
    ).map(
      line => {
        val array = line.split(" ")
        array(6)
      }
    )
    rdd1.collect().foreach(println)
    sc.stop()
  }

  def main2(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(1 to 10) //range
    //    TODO 从数据集中抽取一部分数据 数据抽样  hive中的tablesample
    /*抽取的时候 方式有两种 :
    * 1 不放回抽样  不会出现重复数据
    * 2 放回抽样   会出现重复数据
      sample 算子的第一个参数表示抽样是否 放回  true 放回  false 不放回
     如果是不放回抽样: 第二个参数 表示每一条数据被抽取的概率
       如果是放回抽样 :第二个参数表示 每一条数据期望别抽取的次数
       第三个参数 : 表示数据抽取时的随机数种子 seed:
    如果种子相同  抽取种子的随机数是相同的  意味着抽取的数据是一样的
     随机数算法*/
    val sampleRDD = rdd.sample(false, 0.5, 2)
    rdd.sample(false,0.5,2)
    sampleRDD.collect().foreach(println)
    sc.stop()
  }

  def main3(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 1, 2))
    //    TODO  distinct 去重 scala 集合的去重采用HashSet  但是这是单点集合操作
    //    spark RDD的去重不能使用单点 内存不够用
    /*  分布式去重 :
    case _ => map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)*/
    /*Return a new RDD containing the distinct elements in this RDD*/
    val rdd1 = rdd.distinct() //distinct 存在shuffle操作 可以改变分区 不传 默认分区数量相同
    rdd1.collect().foreach(println)
    sc.stop()
  }

  def main4(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 3)

    //TODO 在某些场合  需要减少分区的数量 采用特殊的算子  coalesce(合并联合)可以减少分区 目的是为了减少分区
    //    减少资源的浪费  coalesce合并分区的时候  合并的是临近的两个分区 而不是数据量少的分区
    //    可能会导致数据更加不均衡 多的和多的合并了
    //    coalesce算子的第二个参数 表示是否使用shuffle  默认为false  改成true
    // 可以使数据更加均衡
    /*TODO coalesce 算子 第一个参数表示修改分区的数量  coalesce 默认设计不采用shuffle 数据改变
     * 分区后 不会打乱重新组合   如果不采用shuffle coalesce 算子是不能够增大分区的  数据不会打乱
      * 如果有shuffle 分区是可以扩大的  shuffle 可以打乱数据 但是不一定 让数据均衡
      * TODO spark 提供了一个专门用于改变分区的算子 coalesce 专门用于减少分区
      * TODO 如果要增加分区 用: repartition 底层用的就是coalesce(numPartitions, shuffle = true)*/
//        val rdd1 = rdd.coalesce(2,true)
    /*Return a new RDD that has exactly numPartitions partitions.
   *
   * Can increase or decrease the level of parallelism in this RDD. Internally, this uses
   * a shuffle to redistribute data.
   *
   * If you are decreasing the number of partitions in this RDD, consider using `coalesce`,
   * which can avoid performing a shuffle.*/
    val rdd1 = rdd.repartition(6)
    rdd1.saveAsTextFile("outputcoal")
    sc.stop()
  }

  def main5(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6))
    //    TODO  sortBy  将数据按照指定的规则排序  sortBy算子默认为升序  sortBy是有shuffle的 可以
    //    重新改变分区
    //    如果想降序 传递第二个参数  false 降序
    //                         数据本身   排序的维度
    /*Return this RDD sorted by the given key function.*/
    val newrdd = rdd.sortBy(num => num, false) //把XXX当成什么来排
    newrdd.collect().foreach(println)
  }

  def main6(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 2)
    val rdd1 = sc.makeRDD(List(7, 8, 9), 2)
    //    TODO  交集 两个数据集的泛型一致
    println(rdd.intersection(rdd1).collect().mkString(","))
    //    TODO  并集  泛型也要保持一致
    println(rdd.union(rdd1).collect().mkString(","))
    //    TODO 差集  泛型也要保持一致
    println(rdd.subtract(rdd1).collect().mkString(","))
    //    TODO 拉链  要求  分区数相同 分区内的元素个数相同  泛型T 和U 可以相等也可以不相等
    /*Can only zip RDDs with same number of elements in each partition
    * */
    /*Zips this RDD with another one, returning key-value pairs with the first element in each RDD,
   * second element in each RDD, etc. Assumes that the two RDDs have the *same number of
   * partitions* and the *same number of elements in each partition* (e.g. one was made through
   * a map on the other).*/
    println(rdd.zip(rdd1).collect().mkString(","))
  }

  def main7(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("b", 2)))
    /* Extra functions available on RDDs of (key, value) pairs through an implicit conversion.*/
    //    TODO partitionBy  所有kv类型数据操作的方法 在RDD中不存在 kv操作都是在PairRDDFunction类
    //    中完成的   编译出错 二次编译 隐式转换 RDD 成 PairRDDFunction
    //    TODO partitionBy 算子用于将数据进行重分区  coalesce用于缩减 或扩大分区 数据和分区的关系
    //    考虑  partitionBy 主要目的是将数据改变分区  需要传递一个分区器
    //    TODO spark 默认提供了三个 分区器 Hash/Range/Python/ partitioner 常用的是Hash 和Range
    //* Return a copy of the RDD partitioned using the specified partitioner.
    val rdd1 = rdd.partitionBy(new HashPartitioner(2))
    rdd1.saveAsTextFile("hashoutput")
    sc.stop()
  }
  def main8(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("b", 2), ("a", 9)))
    //    TODO reduceByKey  将相同的key的value聚合在一起实现统计操作
    //    reduceByKey 可以将分区和聚合 合二为一   实现 WordCount (2 / 10)
    val rdd1 = rdd.reduceByKey(_ + _) //相同的key v聚合
    rdd1.collect().foreach(println)
    sc.stop()
  }

  def main9(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("b", 2), ("a", 9), ("b", 7)))
    //    TODO 算子 - 转换 - reduceByKey
    //    reduceByKey算子的作用 是将相同的key的value 分在一个组中  然后进行reduce操作
    //    TODO 将reduceByKey将分组和聚合合二为一  可以实现WordCount ( 2 / 10)
    val wordCount = rdd.reduceByKey(_ + _)
    /*TODO Collect方法分析:
    *    * @note This method should only be used if the resulting array is expected to be small, as
   * all the data is loaded into the driver's memory.
   * TODO /**
   * Return an RDD that contains all matching values by applying `f`.
   */
  def collect[U: ClassTag](f: PartialFunction[T, U]): RDD[U] = withScope {
    val cleanF = sc.clean(f)
    filter(cleanF.isDefinedAt).map(cleanF)
  }
  TODO Collect方法可以传入一个偏函数 */
    wordCount.collect().foreach(println) //(a,10)    (b,9)
    sc.stop()
  }

  def main10(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("b", 2), ("a", 9)))
    //    TODO groupBy算子可以采用任意的数据作为分组 的标记
    //    groupByKey算子可以采用数据的key 作为分组的标记  返回结果的类型为元组类型
    //    TODO 如何提高shuffle 的性能 1 花钱 提升硬件性能 2 增加磁盘缓冲区 3 减少磁盘IO
    //    groupByKey在shuffle的时候是无法减少数据的
    //    reduceByKey根据相同的key 聚合v 聚合后的value是可迭代的集合
    // 在shuffle之前提前聚合(combine):预聚合
    // 可以提高性能 性能优于groupByKey
    /*Group the values for each key in the RDD into a single sequence. Hash-partitions the
   * resulting RDD with the existing partitioner/parallelism level. The ordering of elements
   * within each group is not guaranteed, and may even differ each time the resulting RDD is
   * evaluated.
   *
   * @note This operation may be very expensive. If you are grouping in order to perform an
   * aggregation (such as a sum or average) over each key, using `PairRDDFunctions.aggregateByKey`
   * or `PairRDDFunctions.reduceByKey` will provide much better performance.*/
    val rddGroup = rdd.groupByKey() //TODO groupByKey 也可以做WordCount ( 3 /10 )
    val wordCountRDD = rddGroup.mapValues(_.sum) //a 10     b 2
    val wordCountSize = rddGroup.mapValues(_.size) // a  2       b 1
    wordCountSize.collect().foreach(println)
    wordCountRDD.collect().foreach(println)
    //    TODO groupByKey 和groupBy 的区别
    //    groupby不需要考虑数据类型  groupByKey 必须保证数据 kv类型
    //    groupBy按照指定的规则分组  groupByKey必须根据key对value进行分组
    //    返回结果 : groupByKey 返回结果是 (String,  Iterable[Int])  v的集合
    //                groupBy   返回结果   (String,Iterable[(String,Int)])
    //    groupBy的底层还是groupByKey
    /*TODO reduceByKey分完组 有聚合的能力 而groupByKey先根据key进行分组产生一个RDD
    * 然后聚合 又产生一个RDD reduceByKey在落盘之前可以减少数据量 相当于map阶段进行了combiner
    * TODO reduceByKey 可以在Shuffle之前 对分区内的数据进行预聚合 称之为combine
    * TODO 面试问题 reduceByKey和groupByKey的区别
    * 从shuffle的角度讲  都存在shuffle操作  但是reduceByKey可以在shuffle 前对分区内相同的key
    * 进行数据的聚合 combine 功能 这样会减少落盘的数据量
    * groupByKey只是进行分组 不存在数据量减少的问题 reduceByKey 性能比较高
    * 从功能的角度讲 : reduceByKey 其实包含分组和聚合的功能 。 groupByKey只能分组 不能聚合
    * 所以在分组聚合的场合下 推荐使用 reduceByKey 如果仅仅是分组不需要聚合 那么还是使用groupByKey
    * */
  }

  def main11(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2)
    //TODO  取出每个分区内相同key的最大值 然后分区进行相加
    /*【(a,1),(a,2),(b,3)] => [(a,2),(b,3)]
    * [(b,4),(b,5),(a,6)] => [(b,5),(a,6)]        =====>[(a,8),(b,8)]
    * TODO aggregateByKey这个算子 存在函数的柯里化
     * 第一个参数列表中有一个参数
     * 参数为零值 表示计算初始值 zero z 用于数据分区内计算用的  如_+_ 单独一个key的数据无法进行计算
     * 第二个参数列表中有两个参数
     * 第一个参数表示 分区内计算规则  第二个参数表示分区间计算规则 */
    val rdd1 = rdd.aggregateByKey(5)(//相同key的初始值为0
      (x, y) => {
        math.max(x, y) //分区内找出相同key的最大值
      },
      (x, y) => {
        //分区间 把相同key的最大值相加  由于要将相同的key的数据从不同的分区放到一个分区
        //        所以分区间的操作时有 shuffle的
        x + y
      }
    )
    //      TODO aggregateByKey也可以实现WordCount ( 4 / 10 )
    val rdd2 = rdd.aggregateByKey(0)(_ + _, _ + _) //通过相同的key聚合 分区内的计算逻辑
    //    和分区间的计算逻辑都是key相同的两个数据 _+_ ,就可以实现WordCount
    //    TODO  如果aggregateByKey算子的分区内计算逻辑和分区间计算逻辑相同 那么可以使用 foldByKey
    //    算子简化  foldByKey()实现WordCount ( 5 / 10)
    val rdd3 = rdd.foldByKey(0)(_ + _)
    rdd1.collect().foreach(println)
    rdd3.collect().foreach(println)
    sc.stop()
  }

  def main12(args: Array[String]): Unit = {
    //    TODO  求每个key的平均值 将数据List(("a", 88), ("b", 95), ("a", 91), ("b", 93),
    // ("a", 95), ("b", 98))求每个key的平均值
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2)
    //    TODO combineByKey有三个参数  第一个参数表示  当第一个数据数据不符合我们的规则时
    // 用于转换的操作
    /*第二个参数表示  表示分区内计算规则
    第三个参数表示   表示分区间计算规则
    如果将数据格式变成 ("a", 1) => ("a", (1,1)) 就可以做平均值的计算了
    */
    val rdd2 = rdd.combineByKey(
      num => (num, 1),
      (x: (Int, Int), y) => { //分区内 是一个Tuple和一个value聚合操作 Tuple的格式(次数,个数1)
        //      分区内的计算结果都是 (key,(次数,个数))
        (x._1 + y, x._2 + 1) //相同key的次数相加 个数加一
      },
      //      分区间相同的key放一块 两个Tuple 分区间计算
      (x: (Int, Int), y: (Int, Int)) => { //分区间的计算结果相加
        (x._1 + y._1, x._2 + y._2) //相同key的次数相加 和 个数相加
      }
    )
    rdd2.collect().foreach(println)
    sc.stop()
  }

  def main13(args: Array[String]): Unit = {
    //    TODO  combineByKey()的WordCount实现  ( 6 / 10)
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2)
    val rdd1 = rdd.combineByKey(
      num => num,
      (x: Int, y) => { //分区内 相同key  相加就行 ("a",8)("b",8)
        x + y
      },
      (x: Int, y: Int) => { //分区间 相同的key 放到一个分区  相同的key相加  ("a",xxx)
        x + y
      }
    )
    rdd1.collect().foreach(println)
    sc.stop()
  }

  def main14(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5),
      ("a", 6)), 2)
    //    TODO 不同方式实现wordCount  以 它们的区别
    rdd.reduceByKey(_ + _)
    /*TODO reduceByKey(func)
    combineByKeyWithClassTag[V]
    ((v: V) => v,  第一个key的v不做处理直接返回
     func,  分区内的计算规则
     func,   分区间的计算规则  分区内和分区间计算规则相同
     partitioner)  func就是我们传的
     * 聚合函数 _+_  */
    rdd.aggregateByKey(0)(_ + _, _ + _)
    /*TODO aggregateByKey(zeroValue)(seqOp,combOp)
    combineByKeyWithClassTag[U]
    ((v: V) => cleanedSeqOp(createZero(), v), 分区内的第一个key的v和初始值 计算
      cleanedSeqOp,  分区内的计算规则
      combOp,   分区间的计算规则
       partitioner)
     */
    rdd.foldByKey(0)(_ + _)
    /*TODO foldByKey(zeroValue)(func)      combineByKeyWithClassTag[V]
    => ((v: V)cleanedFunc(createZero(), v), 第一个key的v和初始值计算
      cleanedFunc,  分区内的计算规则
      cleanedFunc,  分区间计算规则
       partitioner)*/
    rdd.combineByKey(
      num => num,
      (x: Int, y) => {
        x + y
      },
      (x: Int, y: Int) => {
        x + y
      }
    )
    //   /*TODO combineByKey(createCombiner,mergeValue, mergeCombiners)
    // combineByKeyWithClassTag(
    // createCombiner,  分区内的第一个key的 value的 转换
    // mergeValue, 分区内的计算规则
    // mergeCombiners, 分区间的计算规则
    // defaultPartitioner(self))*/
    //    TODO mapSideCombine: Boolean = true, 上面几个方法的map端默认都是开启预聚合功能的
    //    但是groupByKey没有  map端不支持预聚合功能  默认是hash分区器
    rdd.groupByKey()
    /*combineByKeyWithClassTag[CompactBuffer[V]](
      createCombiner, mergeValue, mergeCombiners, partitioner,TODO  mapSideCombine = false)
    bufs.asInstanceOf[RDD[(K, Iterable[V])]]*/
  }

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5),
      ("a", 6)), 2)
    //    TODO sortByKey()   对key进行排序
    val rdd1 = rdd.sortByKey(false) // 对key进行降序排序
    rdd1.collect().foreach(println)
    sc.stop()
  }
}

// TODO 如果传入的是自定义的对象进行比较 自定义的类需要实现 特质Ordered 并重写compare方法
class UserOrder extends Ordered[UserOrder] {
  override def compare(that: UserOrder): Int = 1
}

object Join_Test {
  def main1(args: Array[String]): Unit = {
    //    TODO Join 两个数据集的操作
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5),
      ("a", 6)))
    val rdd1 = sc.makeRDD(List(("a", 11), ("b", 11)))
    /*数据库设计的三范式  不能有间接依赖
     * user => id, name ,deptId
      * dept => id ,deptname*/
    // TODO spark中Join操作 主要针对 两个数据集中相同 key的数据连接 Join操作可能会产生笛卡尔积
    // 可能会出现shuffle 数据落盘 性能会比较差  所以如果可以替代Join 不推荐使用Join
    //    TODO 主表和从表 主表中的数据一定都有 从表中的数据符合条件的才有  主表在join左边就叫 左连接
    //    主表 join 在右边 就叫 右连接
    val rdd3 = rdd.join(rdd1)
    //     右边的rdd中的数据可能有也可能没有 用Option类型 some none
    val rdd4 = rdd.leftOuterJoin(rdd1) //左连接
    val rdd5 = rdd.rightOuterJoin(rdd1) //右连接
    val rdd6 = rdd.fullOuterJoin(rdd1)
    val rdd7 = rdd.cogroup(rdd1) //先分组 再连接  最多可以将4个rdd组合成一个  connect + group 分组 + 连接
    //    rdd3.collect().foreach(println)
    //    rdd4.collect().foreach(println)
    //    rdd5.collect().foreach(println)
    //    rdd6.collect().foreach(println)
    rdd7.collect().foreach(println)
    sc.stop()
  }

  def main2(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 统计出每一个省份 每个广告 被点击数量排行的Top3
    //    TODO 1 读取数据文件 获取原始数据
    val rddlines = sc.textFile("agent.log")
    //    TODO 2 将原始数据进行结构的转换 line => ((省份,广告),1)
    val wordToOne = rddlines.map(
      line => {
        val datas = line.split(" ")
        ((datas(1), datas(4)), 1)
      }
    )
    //    TODO 3 将转换结构后的进行统计
    //    ((省份,广告),1) => ((省份,广告),sum)
    val wordToSum = wordToOne.reduceByKey(_ + _)
    //    TODO 4 将统计的结果进行结构的转换 将省份独立出来
    //    ((省份,广告),sum) => (省份,(广告,sum))
    val wordChange = wordToSum.map {
      case ((province, adv), sum) => { //模式匹配
        (province, (adv, sum))
      }
    }
    //    TODO 5 将数据按照省份进行分组
    //    (省份,List((广告,sum),(广告1,sum1),(广告2,sum2)))
    val groupRDD = wordChange.groupByKey()
    //    TODO 6 将分组后的数据 根据点击数量进行排行  降序  取前三条  打印

    val top3 = groupRDD.mapValues(
      iter => {
        iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
      }
    )
    top3.collect().foreach(println)
    sc.stop()
  }

  def main3(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 统计出每一个省份 每个广告 被点击数量排行的Top3
    //    TODO 1 读取数据文件 获取原始数据
    val rddlines = sc.textFile("agent.log")
    //    TODO 2 将原始数据进行结构的转换 line => (省份,(广告,1))
    val wordToOne = rddlines.map(
      line => {
        val datas = line.split(" ")
        (datas(1), (datas(4), 1))
      }
    )
    //    TODO 先分组 会有shuffle 就会有落盘 性能就差 所以说groupByKey的方法不如上面的
    //    reduceByKey 提前预聚合 减少了落盘的数据量
    val groupRDD = wordToOne.groupByKey() //返回值类型 (String,Iterable[(String,Int)]
    val top3 = groupRDD.mapValues(
      iter => {
        //        TODO 这里内存中的数据量很大
        val advSum = iter.groupBy(_._1).mapValues(_.size)
        advSum.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
      }
    )
    top3.collect().foreach(println)
    sc.stop()
  }

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 统计出每一个省份 每个广告 被点击数量排行的Top3
    //    TODO 1 读取数据文件 获取原始数据
    val rddlines = sc.textFile("agent.log")
    //    TODO 2 将原始数据进行结构的转换 line => ((省份,广告),1)
    val wordToOne = rddlines.map(
      line => {
        val datas = line.split(" ")
        ((datas(1), datas(4)), 1)
      }
    )
    //    TODO 3 将转换结构后的进行统计
    //    ((省份,广告),1) => ((省份,广告),sum)
    val wordToSum = wordToOne.reduceByKey(_ + _)
    //    TODO 4 将统计的结果进行结构的转换 将省份独立出来
    //    ((省份,广告),sum) => (省份,(广告,sum))
    val wordChange = wordToSum.map {
      case ((province, adv), sum) => { //模式匹配
        (province, (adv, sum))
      }
    }
    val groupRDD = wordChange.groupByKey()
    //    TODO  将分组后的数据 根据点击量进行排行 降序
    //    将排序后的数据取前三
    val top3 = groupRDD.aggregateByKey(ArrayBuffer[(String, Int)]())(// key的初始值
      //  为buffer (a,buffer(1)) a,buffer(3,1)=> a,buffer(3,2,1) => a,buffer(4,3,2,1)=> 取前三
      //  a,buffer(4,3,2)
      (buff, t) => {
        buff.appendAll(t) //把值(广告,sum)放到buff里面
        buff.sortBy(_._2)(Ordering.Int.reverse).take(3) //分区内排序 降序 最后取三个
      },
      /*TODO 先进行分区内的排序操作 再进行分区间的排序操作 效率要比直接放到内存中高 */
      (buffer1, buffer2) => {
        buffer1.appendAll(buffer2) // a,buffer(4,3,2,7,5,1) => 降序取前三 a,buffer(7,5,4)
        //        相同的key只会保留三条数据 不会消耗很大的内存
        buffer1.sortBy(_._2)(Ordering.Int.reverse).take(3)
      }
    )
    top3.collect().foreach(println)
    sc.stop()
  }
}

object sample_Test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(1 to 10)
    //    TODO  抽取数据 数据抽样  1 抽取放回  2 抽取不放回
    //    TODO Sample 抽取数据的方式 true表示抽取放回  false 表示抽取不放回
    //    如果是抽取不放回的场合  参数表示每条数据被抽取的几率  抽取的数据条数不一定
    //    如果是抽取放回的场合  参数表示每条数据希望被抽取的次数
    //    val rdd1 = rdd.sample(false,0.5)
    //    val rdd1 = rdd.sample(true,2) //放回抽样 希望抽取的次数是两次 但是不一定是两次
    //    fraction 分数  seed 种子  TODO 不放回抽样 第三个参数是随机数种子  随机数不随机
    //    所谓的随机数 是随机算法获取的一个数  如果随机数种子固定 以后的随机数通过同样的随机算法计算的
    //    结果一样
    var rdd1 = rdd.sample(false, 0.5, 1)
    /* withScope {
      require(fraction >= 0.0, "Negative fraction value: " + fraction)
      if (withReplacement) {
        new PartitionwiseSampledRDD[T, T](this, new PoissonSampler[T](fraction), true, seed)
      } else { //TODO 泊松分布
        new PartitionwiseSampledRDD[T, T](this, new BernoulliSampler[T](fraction), true, seed)
      }  TODO 伯努利分布
      正态分布:
正态分布(Normal distribution)又名高斯分布(Gaussiandistribution),若随机变量X服从一个数学期望为μ、方差为σ^2的高斯分布,记为N(μ,σ^2)。其概率密度函数为正态分布的期望值μ决定了其位置,其标准差σ决定了分布的幅度。我们通常所说的标准正态分布是μ = 0,σ = 1的正态分布。
当μ=0,σ=1时,正态分布就成为标准正态分布N(0,1)。概率密度函数为:
正态分布的密度函数的特点是:关于μ对称,并在μ处取最大值,在正(负)无穷远处取值为0,在μ±σ处有拐点,形状呈现中间高两边低,图像是一条位于x轴上方的钟形曲线。
以上摘自:https://blog.csdn.net/zhaozhn5/article/details/78336366
概述:量存在正态分布,比如同一个人测量一件物品长度的误差、比如相同环境下同一种族的身高分布。
泊松分布:
在统计学上,只要某类事件满足三个条件,它就服从"泊松分布"。三个条件分别是:1、事件X的发生是小概率事件。2、事件X的发生是随机而且互相独立的。3、事件X发生的概率相对稳定。
泊松分布的公式如下:
    P(X=k)=\frac{e^{-\lambda}\lambda^k}{k!}
各个参数的含义:单位时间(或单位面积)内随机事件的平均发生率;k事件X发生的频数;P(X=k)事件X发生k次的概率。
泊松分布与二项分布的关系
当二项分布的n很大而p很小时,泊松分布可作为二项分布的近似,其中λ为np。通常当n≧20,p≦0.05时,就可以用泊松公式近似得计算。事实上,泊松分布也是由二项分布推导而来。
应用实例:http://www.ruanyifeng.com/blog/2013/01/poisson_distribution.html
泊松分布的期望为E(X)=λ,方差D(X)=λ。
伯努利分
以下内容节选自百度百科:
一个非常简单的试验是只有两个可能结果的试验,比如正面或反面,成功或失败,有缺陷或没有缺陷,病人康复或未康复。为方便起见,记这两个可能的结果为0和1,下面的定义就是建立在这类试验基础之上的。
如果随机变量X只取0和1两个值,并且相应的概率为:
则称随机变量X服从参数为p的伯努利分布,若令q=1一p,则X的概率函数可写为:
伯努利分布的期望E(X)=p,D(X)=p(1-p)。
n重伯努利分布的期望E(X)=np,D(X)=np(1-p)。
      */
    rdd1.collect().foreach(println)
  }
}

object partitionRelease {
  def main(args: Array[String]): Unit = {
    //    TODO  算子 转换 缩减分区  或者  shuffle重新洗牌
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(1 to 10, 3)
    //    TODO 缩减 (合并) 缩减分区其实就是合并部分分区 默认缩减分区不会shuffle打乱数据
    //    这里合并的依据并不是数据的多少 而是依据 首选位置 离得近的合并 并不能解决数据倾斜问题
    //    TODO 进行缩减分区的时候 shuffle 打乱数据  数据会均匀一些 不是绝对均匀
    val rdd1 = rdd.coalesce(2)
    val rdd3 = rdd.coalesce(2, true)
    rdd.saveAsTextFile("outputCoalesce")
    rdd1.saveAsTextFile("outputrdd")
    rdd3.saveAsTextFile("outputShuffle")
    sc.stop()
  }
}

object Operator_Action {
  def main1(args: Array[String]): Unit = {
    //    TODO main 方法是一个应用程序 或Driver程序 一个应用程序里面可能有多个作业
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 行动算子 Spark RDD 方法分为两大类 其中一个是转换算子 一个为行动算子
    /*TODO 转换算子只是功能逻辑的封装 不会自己执行  行动算子在被调用的时候 会触发 Spark作业
    * 的执行  只要调用行动算子就会触发
    * TODO collect()方法就是行动算子  rdd.collect() new一个job 行动算子执行时 会构建新的作业
    * 一个行动算子对应一个作业
    * TODO 回顾总结  全落盘和聚合之后再落盘 明显数据量先聚合 会少很多 reduceByKey的性能会高很多
    * 与聚合相关的算子 reduceByKey  aggregateByKey foldByKey combineByKey 通过源码解析他们
    * 之间的关系 reduceByKey 其实就是分区内和分区间的计算规则相同 第一个参数 key的zerovalue
    * 初始值不做处理 直接返回 aggregateByKey有个零值 初始值 和第一个值两个值一块做分区内的计算
    * 分区内和分区间的规则可能不一样 也可能一样 如果规则一样可以简化成 foldByKey
    * combineByKey意思是说 如果我们的数据格式不满足要求 通过第一个括号的参数来改变第一个key的value
    * 的格式 不改也行 这几个算子都有map端的预聚合 map的combine map端会有shuffle 把完整的流程一分
    * 为2 写文件称为shufflemap 读文件称之为shufflereduce 又叫上游阶段和下游阶段
    * TODO 之前学的转换算子将旧的RDD转换成新的RDD 目的是为了扩展功能 和功能的叠加 通过不同功能
    * 组合完成具体的业务 通过装饰者设计模式将不同的功能组合在一块  TODO 类似于IO流中的高级流 包装
    * 低级流 真正实现读的功能的还是低级流 而高级流只是起到装饰拓展功能的作用 比如增加缓冲区 将字节流
    * 转换成字符流
    * TODO 与面试官面试的时候 有意识的将不同的知识点穿插到一块 RDD就是一个数据模型 更适合并行计算
    * 重复使用 */
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //两个分区 先分区内计算 在分区间计算求和
    val i = rdd.reduce(_ + _) // 会返回结果 之前的reduceByKey...会产生新的RDD
    //    TODO 行动算子会得到结果 转换算子会产生新的RDD 算子和方法的区别 分布式和单点
    /*TODO reduce()方法的底层:  sc.runJob(this, reducePartition, mergeResult)
    // Get the final result out of our Option, or throw an exception if the RDD was empty
    jobResult.getOrElse(throw new UnsupportedOperationException("empty collection"))
  }
  TODO 此处的reduce和scala中的reduce不同:此处的reduce是算子 而scala中的reduce是方法
  算子和方法的区别 :算子 可以有不同的分区 分布式计算 */
    println(i)
    //    TODO collect() 行动算子 将数据从Executor端采集到Driver端
    //    collect()会将数据全部拉取到Driver端的内存中 形成数据集合 可能会导致内存溢出 一般保存到磁盘
    //    的分区文件中
    val array = rdd.collect() //返回值类型 Array[Int]
    println(array.mkString(","))
    val l = rdd.count() //TODO count()行动算子 计算元素的个数
    println(l)
    //    TODO first() scala中集合的方法有 head  tail init  last
    val int = rdd.first() //获取元素的第一个值
    //    TODO take() 取几条数据
    val arraytake = rdd.take(3) //返回值类型是 Array[Int]
    println(arraytake.mkString(","))
    //    TODO  takeOrdered 行动算子
    val arrayOrder = rdd.takeOrdered(3) //排完序后 取前三个
    println(arrayOrder.mkString(","))
    sc.stop()
  }

  def main2(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //    TODO aggregate 和 aggregateByKey 的区别
    //    1 数据格式  2 aggregateByKey 是一个转换算子 所以执行后 会产生新的RDD
    //    3 aggregate是一个行动算子 执行后会得到结果
    //    4 aggregateByKey执行计算的时候 初始值只会参与分区内的计算
    //  TODO   aggregate 在执行计算的时候  初始值 不仅仅会参与分区内的计算 也会参与分区间的计算
    /*下面的结果是25 原因: [1,2] [3,4] =>   [1,2,5] [3,4,5]  => [8] [12] =>[5,8,12]
    * => 25*/
    val i = rdd.aggregate(5)(_ + _, _ + _) //和aggregateByKey的区别是这里没有key的概念
    println(i)
    rdd.fold(5)(_ + _) //TODO fold 如果aggregate 分区内的计算方法和分区间的计算方法一样的话
    //    可以简写成 fold 这些方法和scala的思想是一样的 只不过spark中的是算子 分布式的 针对的是RDD
    sc.stop()
  }

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //    TODO  countByKey 统计相同的key出现了多少次  可以通过countByKey实现WordCount
    /*TODO 思路 先扁平化 拆成 ("a",4) => ("a",1)("a",1)("a",1)("a",1)
    * 把一个完整的对象中的每一个属性拆出来 叫做扁平化 把Tuple中第一层的元素拆分叫做扁平化
    * 把单一的对象 拆成多个对象也叫扁平化 TODO countByKey实现WordCount ( 7 / 10)
     * */
    val rdd1 = rdd.map(("a", _))
    val stringToLong = rdd1.countByKey()
    println(stringToLong) //Map(a -> 4)
    //    TODO countByValue()的value不是kv键值对中v的意思 而是kv整体出现的次数
    //   集合中的每一个值出现了多少次
    /*RDD的算子 在处理数据上分为三大类 : 1 单value类型  双value类型  k-v类型
    * TODO countByValue实现WordCount ( 8 / 10)  将数据格式进行转换("a",4) =>
    * "a" ,"a", "a" , "a"
    * TODO  准备考试 WordCount  百度去查10 种WordCount*/
    val tupleToLong = rdd1.countByValue()
    println(tupleToLong)
    rdd.saveAsTextFile("outputsave") //保存成分区文件
    rdd.saveAsObjectFile("outputObject") //保存成序列化文件
    //    rdd.saveAsSequenceFile() 保存成序列文件
    sc.stop()
  }
}

object action_Foreach {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //    TODO 行动算子 foreach
    //    collect()是按照分区号码进行采集的 先采集0号分区.... 所以它的采集顺序和输入顺序是一样的
    rdd.collect().foreach(println) //将collect返回给Driver端的结果进行foreach
    println("**************************************************")
    rdd.foreach(println) //在Executor端进行遍历打印 如果有多个分区 多个executor 打印出的结果
    //    分区内有序 分区间无序
    sc.stop()
  }
}

object Little_Test_2 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List[Int](), 2)
    //    TODO 算子  - 行动
    val user = new User //在Driver的内存中创建了 user对象
    rdd.foreach(
      num => {
        println(user.age + num) //需要将Driver中创建的user对象通过网络传给两个Executor User需要
        //        序列化
        //        TODO scala 语法 :匿名函数用到了外部的变量 把它包含到内部 称为闭包
        //        TODO Spark在执行算子的时候 如果算子的内部使用了外部的变量(对象) 那么意味着一定会
        //        出现闭包 出现闭包的三种情况: 1 匿名函数一定会有闭包  2 内部函数在外部使用 也会有闭包
        //        原因是改变了 函数函数的生命周期  3 把一个函数当做对象使用也会有闭包
        //        在这种场景中  需要将Driver端的变量通过网络传输传递给Executor执行 这个操作不用
        //        执行也能判断出来  所以可以在执行前 对数据进行序列化校验
        //        TODO 在foreach执行runJob之前 执行 sc.clean(f)
        /*private[spark] def clean[F <: AnyRef](f: F, checkSerializable: Boolean = true): F = {
    ClosureCleaner.clean(f, checkSerializable)
    f
  } TODO 判断是否是闭包  检查是否序列化  称为闭包检测
   TODO  spark在执行作业之前 需要进行闭包检测功能
   从计算的角度 算子以外的代码 都是在Driver端执行 算子里面的代码都是在Executor端执行 那么在scala
   的函数式编程中,就会导致算子内经常会用到算子外的数据 这样就形成了闭包的效果 如果使用算子外的数据
   无法序列化 就意味着无法传值给Executor端执行 就会发生错误 所以需要在执行任务前 检测闭包内的对象
   是否可以进行序列化  这个操作我们称之为闭包检测 2.12版本后闭包编译方式发生了改变  */
      }
    )
    sc.stop()
  }

  class User extends /*Serializable*/ {
    val age = 30
  }

}

object Serial_Test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("RDD")
    val sc = new SparkContext(conf)
    //    TODO 行动算子
    val rdd = sc.makeRDD(List("Hello", "Hive", "Spark", "Scala"))
    val s = new Search("S")
    s.filterByQuery(rdd).collect().foreach(println)

  }

  /*case*/ class Search(q: String) /*extends Serializable*/ {
    //    这里额外的方法需要访问 构造方法的一个参数 所以这个参数提升为Search的属性
    //    q 是属性 属性和类相关 类需要序列化 如果不继承Serializable 用case 也行
    //   样例类 是专门为模式匹配声明的类 除了模式匹配能用外 也能当成普通类来用 生成类的同时
    //   自动添加了很多功能  其中有一项就是默认实现了可序列化接口
    def filterByQuery(rdd: RDD[String]): RDD[String] = {
      val s = this.q // Driver内
      rdd.filter(_.startsWith(s /*this.q*/)) //这样写的话 Executor中用不到q 用到了s s只是普通的
      //      字符串 没有涉及到对象
    }
  }

  class Test(name: String) { //反编译之后为了让方法test能够用到传给构造器Test的name将name
    //    提升为Test的属性
    def test(): Unit = {
      // 如果去掉这个方法 那么反编译之后 name 就不会别提升为 Test类的属性
      println(name)
    }
  }

}

object Kryo_Serial_Test {
  def main(args: Array[String]): Unit = {
    //    TODO hadoop中的压缩:减少磁盘使用量 减少网络IO  Java的序列化是重量级的 序列化后 对象的提交
    //    比较大 Spark处于性能的考虑  从2.0开始 支持另外一种序列化 Kyro 序列化机制 速度是Serializable
    //    10倍
    //    TODO 当RDD shuffle数据的时候 简单数据类型 数组 字符串类型 已经在Spark内部使用Kryo来序列化
    //    TODO RDD 依赖 C  ---依赖--> B ----依赖----> A    C和A有血缘关系
    //    相邻的两个RDD称之为依赖关系  多个连续的依赖关系称之为 血缘关系
    //    File => textFile => flatMap => map => reduceByKey
    //    TODO RDD 一定会保存血缘关系  没有血缘关系 一旦中间某个RDD出现错误 无法重新计算
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    val lines = sc.textFile("text.txt")
    println("lines的血缘 :" + lines.toDebugString)
    val arrayline = lines.flatMap(_.split(" "))
    println("arrayline的血缘:" + arrayline.toDebugString)
    val wordToOne = arrayline.map((_, 1))
    //    TODO 双向的一对多 是多对多
    println("wordToOne的依赖 :" + wordToOne.dependencies)
    println("wordToOne的血缘" + wordToOne.toDebugString)
    val wordCount = wordToOne.reduceByKey(_ + _)
    println("wordCount的依赖: " + wordCount.dependencies)
    println("wordCount的血缘 :" + wordCount.toDebugString)
    /*wordCount的血缘 :(1) ShuffledRDD[4] at reduceByKey at RDD_Instance_Memory.scala:1233 []
 +-(1) MapPartitionsRDD[3] at map at RDD_Instance_Memory.scala:1231 []
    |  MapPartitionsRDD[2] at flatMap at RDD_Instance_Memory.scala:1229 []
    |  text.txt MapPartitionsRDD[1] at textFile at RDD_Instance_Memory.scala:1227 []
    |  text.txt HadoopRDD[0] at textFile at RDD_Instance_Memory.scala:1227 []*/
    wordCount.collect().foreach(println)
    //    TODO RDD只支持粗粒度 转换 即在大量记录上执行的单个操作 将创建RDD的一系列血缘保存下来
    //    以便恢复丢失的分区 RDD的Lineage会记录RDD的元数据信息和转换行为 当该RDD的部分分区数据丢失
    //    时 他可以根据这些信息 来重新运算和恢复丢失的数据分区
    sc.stop()
    //    TODO 依赖关系分为两大类 :窄依赖 OneToOneDependency & 宽依赖 ShuffleDependency
    /*TODO 上游旧的RDD的一个分区的数据 被下游RDD一个分区所独享 称之为窄依赖
    * 上游旧的RDD的一个分区的数据 被下游的RDD的多个分区共享 称之为宽依赖  上游中两个分区的数据
    * 进入到下游一个分区 被这一个分区独享也称为 窄依赖
    * 这里所说的依赖关系:其实就是相邻的两个RDD之间的关系
    * TODO 窄依赖 表示上游的RDD的partition最多被下游RDD的一个partition使用 窄依赖 形象的比喻
    * 为独生子女
    * 宽依赖 表示 同一个父(上游)RDD的partition被多个(下游)RDD的partition依赖 会引起shuffle
    * 宽依赖 比喻为多生 一般情况下 宽依赖 = ShuffleDependency
    *
    * TODO  阶段划分
    *   // New stage creation may throw an exception if, for example, jobs are run on a
      // HadoopRDD whose underlying HDFS files have been deleted.
      finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
/ /创建新阶段可能抛出异常,例如,工作是运行在一个 HadoopRDD的底层HDFS文件已被删除。

  TODO val parents = getOrCreateParentStages(rdd, jobId) resultStage有没有上一级的阶段
  传入一个RDD 这个RDD 是collect前面的RDD 也就是wordCount
   getShuffleDependencies(rdd).map { shuffleDep =>
      getOrCreateShuffleMapStage(shuffleDep, firstJobId)
    }.toList TODO 判断传入的rdd有没有shuffle依赖 wordCountRDD 有shuffle依赖 如果有的话
     返回 : HashSet集合
     RDD1           RDD2              ;  RDD3
          shuffleMapStage  写文件操作 ; 读文件操作
          TODO 阶段划分 - 源码
          阶段划分主要是针对shuffle的操作 第一个阶段是写的阶段 第二个阶段是读的阶段
          或者叫第一个是map阶段 第二个是reduce阶段 必须保证前一个阶段执行完毕才能走另外一个阶段
          因为shuffle是要落盘的 必须保证上一个阶段所有的数据落盘
          TODO 所有的RDD的操作会形成一个完整的阶段:叫做ResultStage 这个阶段可能会因为shuffle
          等操作一分为2
          val parents = getOrCreateParentStages(rdd, jobId) 上一个阶段
    val id = nextStageId.getAndIncrement()
    val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite)
    上一个阶段被包含在了 ResultStage里面

   TODO 源码: /**
   * Returns shuffle dependencies that are immediate parents of the given RDD.
   **返回洗牌直接给定抽样的父母的依赖关系。
   * This function will not return more distant ancestors. *这个函数不会返回更遥远的祖先。
    * For example, if C has a shuffle 例如,如果C洗牌
   * dependency on B which has a shuffle dependency on A:
   **依赖B有洗牌依赖:
   * A <-- B <-- C
   * calling this function with rdd C will only return the B <-- C dependency.
   **与抽样C调用这个函数只会返回B < - C的依赖。
   * This function is scheduler-visible for the purpose of unit testing.
   */*这个函数是scheduler-visible为目的的单元测试。
  private[scheduler] def getShuffleDependencies(
      rdd: RDD[_]): HashSet[ShuffleDependency[_, _, _]] = {
    val parents = new HashSet[ShuffleDependency[_, _, _]]
    val visited = new HashSet[RDD[_]]
    val waitingForVisit = new ListBuffer[RDD[_]]
    waitingForVisit += rdd      把RDD加到集合里面
    while (waitingForVisit.nonEmpty) { 判断集合是否为空
      val toVisit = waitingForVisit.remove(0)  toVisit就是刚刚添加的RDD
      if (!visited(toVisit)) { 如果这个RDD没有被访问过
        visited += toVisit
        toVisit.dependencies.foreach {
          case shuffleDep: ShuffleDependency[_, _, _] =>
            parents += shuffleDep  集合parents增加一个依赖
          case dependency =>
            waitingForVisit.prepend(dependency.rdd)
        }
      }
    }
    parents  返回添加完shuffle依赖的集合parents
    TODO 有一个shuffle map之后 转换成 shufflemapstage
  Get or create the list of parent stages for a given RDD.  The new Stages will be created with
   * the provided firstJobId.
   *TODO getShuffleDependencies(rdd).map { shuffleDep =>
      getOrCreateShuffleMapStage(shuffleDep, firstJobId)
    }.toList
  }
  TODO 结论 阶段的数量: shuffle操作的个数 + 1 有几个shuffle依赖就有几个shufflemapstage
  然后再加上一个大范围的resultStage*/
  }
}

object Job_Stage {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    //    TODO RDD任务划分 RDD任务划分为
    // Application :初始化一个SparkContext即生成一个Application
    // Job :一个行动Action算子生成一个Job
    // Stage : Stage 等于宽依赖shuffleDependency的个数加 1
    // Task :一个Stage 阶段中,最后一个RDD的分区个数就是Task的个数
    /*TODO 注意 : Application -> Job -> Stage -> Task 每一层都是1对n的关系
    * 源码中 Task的计算 :
    * /** Submits stage, but first recursively submits any missing parents. */
    *   private def submitStage(stage: Stage): Unit = {
    val jobId = activeJobForStage(stage)
    if (jobId.isDefined) {
      logDebug(s"submitStage($stage (name=${stage.name};" +
        s"jobs=${stage.jobIds.toSeq.sorted.mkString(",")}))")
      if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
        val missing = getMissingParentStages(stage).sortBy(_.id) 获取丢失的上一级阶段
        logDebug("missing: " + missing)
        if (missing.isEmpty) { 如果没有上一级阶段
          logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
          submitMissingTasks(stage, jobId.get) 提交丢失的Task 提交上一级的任务用的
        } else { 如果有上一级阶段
          for (parent <- missing) {
            submitStage(parent) 上一级阶段挨个遍历 提交 此处用了递归
          }
          waitingStages += stage
        }
      }
    } else {
      abortStage(stage, "No active job for stage " + stage.id, None)
    }
  }
  TODO 进入 submitMissingTasks(stage, jobId.get)

    val tasks: Seq[Task[_]] = try {
      val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
      stage match {
        case stage: ShuffleMapStage =>  Stage 模式匹配
          stage.pendingPartitions.clear()
          partitionsToCompute.map { id =>  0   1   2 三个分区分别传入
            val locs = taskIdToLocations(id)
            val part = partitions(id)
            stage.pendingPartitions += id
            new ShuffleMapTask(stage.id, stage.latestInfo.attemptNumber, 三个分区new了三个Task
              taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId),
              Option(sc.applicationId), sc.applicationAttemptId, stage.rdd.isBarrier())
          }

        case stage: ResultStage =>
          partitionsToCompute.map { id =>
            val p: Int = stage.partitions(id)
            val part = partitions(p)
            val locs = taskIdToLocations(id)
            new ResultTask(stage.id, stage.latestInfo.attemptNumber,
              taskBinary, part, locs, id, properties, serializedTaskMetrics,
              Option(jobId), Option(sc.applicationId), sc.applicationAttemptId,
              stage.rdd.isBarrier())
          }
      }
    }
  TODO 进入 // Figure out the indexes of partition ids to compute.
      val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
 TODO 进入 findMissingPartitions
/** Returns the sequence of partition ids that are missing (i.e. needs to be computed). */
  override def findMissingPartitions(): Seq[Int] = {
    mapOutputTrackerMaster
      .findMissingPartitions(shuffleDep.shuffleId)
      .getOrElse(0 until numPartitions)  0 1 2  0到numPartitions 不包含numPartitions
  }
  val numPartitions = rdd.partitions.length
   TODO 总结 :任务的数量应该是所有的阶段的最后一个RDD的分区数之和
    ************************************************************************************
    *                                                       ResultStage                            *
    *                                                                                   *
    *           shffuleMapStage                                                                        *
    *                                                     RDD                              *
    *         RDD         RDD                                                          *
    *
    *                                                                                   *
    *                                                                                   *
    *                                                                                   *
    *                                                                                   *
    * ************************************************************************************
    * 上面的例子中总共有6个Task
    * TODO 一个阶段会根据最后一个RDD的分区来决定Task的数量 一个阶段就对应多个Task
    * */
  }
}

object Persist_Test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    sc.setCheckpointDir("cp")
    //    TODO  RDD通过Cache 或者Persist方法将前面的计算结果缓存 默认情况下会把数据缓存到JVM的堆内存中 但并不是这两个方法
    //    被调用的时候立即缓存 而是触发后面的action算子时 该RDD将会被缓存在计算阶段的内存中 并供后面重用
    //    TODO Cashe操作会增加血缘关系 但是不会改变原来的血缘关系
    //    Cashe方法可以将血缘关系修改 添加一个和缓存相关的依赖关系  该操作不安全 因为将数据放到内存中 因此另一个方法放到磁盘
    //    中 persist cashe的底层调用的就是persist 但是它的持久化级别是 Memory_Only 一旦内存不够就丢弃数据 根据LRU算法
    //    StorageLevel 存储级别 : OFF_HEAP 堆外内存
    /*System.gc() gc线程 - 守护线程 守护线程是为用户服务的线程   堆中的数据有10个g的垃圾 但是垃圾回收器不及时 又来了2
    * 个g的数据 堆外内存其实就是不受JVM 管理的内存  是主动向OS 申请的内存  Java虚拟机 默认内存是最大内存的 1/64
    * 最大内存是 1/4 */
    //    CachedPartitions: 1; MemorySize: 1648.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
    val linesRDD = sc.textFile("text.txt")
    val lineArray = linesRDD.flatMap(_.split(" "))
    val wordToOne = lineArray.map(
      t => {
        println("*****************************")
        (t, 1)
      }
    )
    //    持久化 wordToOne  TODO 如果持久化的话 持久化的文件只能自己用 而且使用完毕后 会删除
    //    wordToOne.cache()
    //    wordToOne.persist(StorageLevel.DISK_ONLY_2)//磁盘 两个副本
    //    TODO 检查点 Spark可以将中间计算的结果保存到检查点中 让其他的应用 使用数据
    //    检查点可以切断 血缘关系 。可以剥离出去 以文件的形式存储
    //    TODO 检查点为了数据的安全 会重新执行一遍作业 会执行两次  导致效率降低 为了解决这个问题
    //    可以将检查点和缓存联合 使用
    //    TODO  缓存和检查点的区别:
    /* 1 Cashe缓存只是将数据保存下来,不切断血缘关系。CheckPoint检查点切断血缘关系依赖
    *  2 Cashe缓存的数据通常存储在磁盘、内存等地方,可靠性低,Checkpoint的数据通存储在HDFS
    *  分布式文件系统中。可靠性高
    *  3 建议对Checkpoint的RDD使用Cashe缓存,这样Checkpoint的job只需从Cashe缓存中读取数据即可
    *  否则需要重新计算一次RDD*/
    wordToOne.cache()
    wordToOne.checkpoint() //Checkpoint directory has not been set in the SparkContext
    val WordCount = wordToOne.reduceByKey(_ + _)
    println(WordCount.toDebugString)
    WordCount.collect().foreach(println)
    println(WordCount.toDebugString)
    println("--------------------------------")
    val groupRDD = wordToOne.groupBy(_._1)
    groupRDD.collect()
    sc.stop()
    /*Exception in thread "main" java.lang.IllegalStateException: SparkContext has been shutdown*/
  }
}

object Patitioner_Test {
  def main1(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    val lines = sc.makeRDD(
      List(
        ("ff14", "maomao"),
        ("af15", "lalafei"),
        ("ff14", "nanjing"),
        ("cf", "youxi")
      ), 2
    )
    //    TODO 自定义分区
    val myRDD = lines.partitionBy(new MyPartitioner)
    myRDD.saveAsTextFile("MPoutput")
    sc.stop()
  }

  /*TODO 自定义分区器  1 继承Partitioner 2 重写方法 */
  class MyPartitioner extends Partitioner {
    //    TODO 分区数量
    override def numPartitions: Int = 3

    // TODO 根据数据的key返回所在的分区编号 分区编号从0开始
    override def getPartition(key: Any): Int = {
      key match {
        case "ff14" => 0
        case "af15" => 1
        case "cf" => 2
      }
    }
  }

  //    TODO 假如有两个连续的reduceByKey
  def main2(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    val lines = sc.makeRDD(
      List(
        ("ff14", "maomao"),
        ("af15", "lalafei"),
        ("ff14", "nanjing"),
        ("cf", "youxi")
      ), 2
    )
    val rdd1 = lines.reduceByKey(_ + _)
    val rdd2 = rdd1.reduceByKey(_ + _) // 这个reduceByKey就不会走shuffle了
    /*TODO reduceByKey 源码:
    *   def reduceByKey(func: (V, V) => V): RDD[(K, V)] = self.withScope {
        reduceByKey(defaultPartitioner(self), func)
      } TODO 如果没有传分区器 :new HashPartitioner(defaultNumPartitions)
      /**
       * Return a copy of the RDD partitioned using the specified partitioner.
       */
      def partitionBy(partitioner: Partitioner): RDD[(K, V)] = self.withScope {
        if (keyClass.isArray && partitioner.isInstanceOf[HashPartitioner]) {
          throw new SparkException("HashPartitioner cannot partition array keys.")
        }
        if (self.partitioner == Some(partitioner)) {  双等号就是非空equals
          self   如果是true 直接返回自身 RDD不做任何处理
        } else {
          new ShuffledRDD[K, V, V](self, partitioner)
        }
      }

     默认的HashPartitioner重写了equals  比较的规则:
     override def equals(other: Any): Boolean = other match {
        case h: HashPartitioner =>  如果分区器相同且分区个数相同,返回true
          h.numPartitions == numPartitions
        case _ =>
          false
      }
    */
  }

  def main3(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    val lines = sc.makeRDD(
      List(
        ("ff14", "maomao"),
        ("af15", "lalafei"),
        ("ff14", "nanjing"),
        ("cf", "youxi")
      ), 2
    )
    //    TODO 文件的保存和读取
    lines.saveAsObjectFile("Objectoutput")
    val rdd1 = sc.objectFile[(String, String)]("Objectoutput")
    lines.saveAsSequenceFile("sequenceFileoutput")
    rdd1.collect().foreach(println)
    val rdd2 = sc.sequenceFile[String, String]("sequenceFileoutput")
    rdd2.collect().foreach(println)
    sc.stop()
  }

  def main4(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4))
    var sum = 0 //sum和Spark没有关系 而RDD是spark的数据处理模型 rdd.collect()可以将rdd返回给Driver端
    //    TODO 自定义的变量和RDD没有关系 RDD无法计算该变量
    //    TODO 累加器 分布式共享只写变量  long型累加器 double浮点型累加器 集合累加器
    //    创建累加器
    /*TODO kafka集群生产数据: kafkaProducer main线程将数据放到缓冲区  sender线程往broker发送
    * 数据  producer端有一个双端队列 Dqueue 在缓冲区中 */
    val sum1 = sc.longAccumulator("sum")

    rdd.foreach( //foreach返回值是Unit
      num => {
        //        sum = sum + num
        //        println(sum) //这样打印的话 打印的结果是 1 3 6 10
        //        TODO 使用累加器 获取累加器的结果
        sum1.add(num)
      }
    )
    //    获取累加器的结果
    println("累加器的值" + sum1.value)
    println(sum) //Driver端打印sum sum为0
    sc.stop()
  }

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List("scala", "scala", "scala", "scala", "scala", "scala", "spark", "spark"
      , "spark", "spark", "spark", "spark", "spark", "spark"))
    //    TODO 采用累加器实现WordCount
    //    创建累加器
    val accumulator = new wordCountAccumulator()
    //    TODO 向Spark进行注册  spark将累加器结果进行返回
    sc.register(accumulator, "wordCount")
    rdd.foreach(
      word => {
        //        将单词放到累加器中
        accumulator.add(word)
      }
    )
    //    获取累加器的累加结果
    println(accumulator.value)
    sc.stop()
  }

  //  自定义数据累加器  1 继承AccumulatorV2  2 定义泛型 IN String Out Map[k,v]
//  3 重写方法 (6) 3+3  三个计算相关 add merge value输入输出和 合并  三个状态相关 copy reset
//  isZero
  class wordCountAccumulator extends AccumulatorV2[String, mutable.Map[String, Int]] {
    private val wcMap = mutable.Map[String, Int]()

    //判断累加器是否为初始状态 no.3  必须返回true 返回false 会报错
    override def isZero: Boolean = {
      wcMap.isEmpty
      /*copyAndReset must return a zero value copy*/
    }

    // 复制累加器  no.1
    override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = {
      new wordCountAccumulator()
    }

    //重置累加器  累加器之间是无法相互读取的 每个累加器执行的时候保证是全新的累加器 no.2
    override def reset(): Unit = {
      wcMap.clear()
    }

    // 从外部向累加器中添加数据
    override def add(word: String): Unit = {
      val oldCount = wcMap.getOrElse(word, 0)
      wcMap.update(word, oldCount + 1)
    }

    // Executor的计算结果返回到Driver进行合并  合并两个累加器的结果 V2表示第二个版本
    override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = {
      other.value.foreach {
        case (word, cnt) => {
          val oldCnt = wcMap.getOrElse(word, 0)
          wcMap.update(word, oldCnt + cnt)
        }
      }
    }
    override def value: mutable.Map[String, Int] = wcMap
  }
}
object broadCast_Test{
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("wordCount")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(
      ("a",1),("b",2)
    ))
//  val rdd1 =  sc.makeRDD(List(
//      ("a",3),("b",4)
//    ))
   val map = mutable.Map[String,Int]( //map是 Driver端声明的 需要发给Executor 四个分区 四个
//  task  如果是两个executor 每个executor两个task 一个task需要执行一遍map中的逻辑
//  rdd只能将数据给task 传 我们需要将Driver端的数据传给Executor TODO 广播变量
//  分布式只读变量
      ("a",3),("b",4)
    )
//    TODO 使用广播变量  将数据放到Executor端的缓存中 减少数据的冗余
    val bcMap = sc.broadcast(map)
//    val rdd3 = rdd.join(rdd1) // TODO  Join操作可能会有笛卡尔积 也可能会有shuffle
//    rdd3.collect().foreach(println) //(a,(1,3))     (b,(2,4))
     val rdd2 = rdd.map{
        case (word,cnt) => {
//          使用了广播变量 用的时候就去Executor的缓存中去取 如果没有 就将Driver中的数据拉取过来
      val cnt1 = bcMap.value.getOrElse(word,0) //map查数据是很快的 底层有hash定位
      (word,(cnt,cnt1))
    }
      }
    rdd2.collect().foreach(println)
    sc.stop()
  }
}
object Tencent{
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("Tencent")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.textFile("tencent.txt")
//    ((uid,enum), value)
    val mapRdd = rdd.map(
      data => {
        val datas: Array[String] = data.split(";")
        for (i <- 0 to 2) {
         val rdd1 =  ((datas(0).split(" = ")(1), datas(1).split(" = ")(1).replace("[", "").replace("]", "").split(",")
          (i)), datas
          (2))
        }
      }
    )
    mapRdd.collect().foreach(println)
    sc.stop()
  }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值