一、RDD概述

1. 什么是RDD
RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark中最基本的数据抽象。代码中是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合。

2.RDD的属性

spark将rdd写入到clickhouse spark中rdd_大数据

  • 一组分区(Partition),即数据集的基本组成单位;
  • 一个计算每个分区的函数;
  • RDD之间的依赖关系;
  • 一个Partitioner,即RDD的分片函数;
  • 一个列表,存储存取每个Partition的优先位置(preferred location)

3. RDD特点
RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。

  • 分区:
    RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个compute函数得到每个分区的数据。如果RDD是通过已有的文件系统构建,则compute函数是读取指定文件系统中的数据,如果RDD是通过其他RDD转换而来,则compute函数是执行转换逻辑将其他RDD的数据进行转换。
  • spark将rdd写入到clickhouse spark中rdd_数组_02

  • 只读:
    如下图所示,RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。
  • spark将rdd写入到clickhouse spark中rdd_spark_03

  • 由一个RDD转换到另一个RDD,可以通过丰富的操作算子实现,不再像MapReduce那样只能写map和reduce了, RDD的操作算子包括两类,一类叫做 transformations,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做 actions,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中。
  • 依赖
    RDD通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖,RDD之间分区是一对一或者多对一的,另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是一对多的关系。
  • spark将rdd写入到clickhouse spark中rdd_大数据_04

  • 缓存
    如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。如下图所示,RDD-1经过一系列的转换后得到RDD-n并保存到hdfs,RDD-1在这一过程中会有个中间结果,如果将其缓存到内存,那么在随后的RDD-1转换到RDD-m这一过程中,就不会计算其之前的RDD-0了。
  • spark将rdd写入到clickhouse spark中rdd_spark_05

  • CheckPoint
    虽然RDD的血缘关系天然地可以实现容错,当RDD的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行,RDD之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此,RDD支持checkpoint将数据保存到持久化的存储中这样就可以切断之前的血缘关系,因为checkpoint后的RDD不需要知道它的父RDD了, 它可以从checkpoint处拿到数据。

二、RDD编程

1.编程模型

  • 在Spark中,RDD被表示为对象,通过对象上的方法调用来对RDD进行转换。经过一系列的 transformations定义RDD之后,就可以调用actions触发RDD的计算,action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行RDD的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。
  • 要使用Spark,需要编写一个Driver程序,它被提交到集群以调度运行Worker,如下图所示。Driver中定义了一个或多个RDD,并调用RDD上的action,Worker则执行RDD分区计算任务。

2.RDD的创建
在Spark中创建RDD的创建方式可以分为三种:从集合中创建RDD;从外部存储创建RDD;从其他RDD创建。

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

/**
 * 生成RDD
 */
object Demo02CreatRdd {
  def main(args: Array[String]): Unit = {
    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)

    /* 初始化SparkContext */
    val sc = new SparkContext(new SparkConf().setAppName(this.getClass.getName).setMaster("local[2]"))

    /* 创建集合 */
    val array = Array(1, 2, 3, 4, 5)

    /* 并行化创建RDD  默认分区2个 */
    /* 1.使用集合 */
    val arrayRDD1: RDD[Int] = sc.parallelize(array)
    println("默认分区: " + arrayRDD1.getNumPartitions)

    /* 并行化创建RDD 指定分区数 */
    val arrayRDD2: RDD[Int] = sc.parallelize(array,5)
    println("指定分区数: " + arrayRDD2.getNumPartitions)

    //变成 kv的Rdd
    val value: RDD[(Int, Int)] = arrayRDD1.map((_,1))

    /* 以元组的形式创建数组 */
    val arr1 = Array((1,1),(2,2),(3,3),(4,5))
    //变成 kv的Rdd
    val numRdd2: RDD[(Int, Int)] = sc.parallelize(arr1)


    // 2.使用外部存储系统   本地   hdfs 创建RDD
    val unit: RDD[String] = sc.textFile("")
  }
}

3.transformations类算子

3.1.Value类型

  • map(func)
    作用:返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成,每次处理一条数据。
  • mapPartitions(func)
    作用:类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]。假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区。
    每次处理一个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才能释放,可能导致OOM。
    开发指导:当内存空间较大的时候建议使用mapPartition(),以提高处理效率。
  • mapPartitionsWithIndex(func)
    作用:类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U];
import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable

object Demo03Transformation {

  /* 测试map算子 */
  def map(sc: SparkContext) = {
    /* 创建数组 */
    val array = Array(1, 2, 3, 4, 5, 6, 9)
    /* 生成RDD */
    val arrayRDD: RDD[Int] = sc.parallelize(array)
    /* 用map算子聚合 将RDD中每个元素乘2 */
    val mapRDD: RDD[Int] = arrayRDD.map(_ * 2)
    /* 调用Action算子的 collect() 将RDD转成数组 */
    val array1: Array[Int] = mapRDD.collect()
    /* 转成可变的数组  */
    val buffer: mutable.Buffer[Int] = array1.toBuffer
    println(buffer)
  }

  /* 测试 mapPartition方法 */
  def mapPartition(sc: SparkContext) = {
    /* 创建数组 */
    val array = Array(8, 1, 3, 4, 5, 6, 9)
    /* 并行化生成RDD */
    val arrayRDD: RDD[Int] = sc.parallelize(array)
    /* 对整个分区操作 */
    val mapPartitionsRDD: RDD[Int] = arrayRDD.mapPartitions(nums => {
      /* 元素乘10 */
      nums.map(_ * 10)
    })
    /* foreach遍历RDD */
    mapPartitionsRDD.foreach(println(_))
  }

  /* 测试mapPartitionWithIndex */
  def mapPartitionWithIndex(sc: SparkContext) = {
    /* 创建数组 */
    val array = Array(8, 1, 3, 4, 5, 6, 9)
    /* 并行化生成RDD 默认两个分区 */
    val arrayRDD: RDD[Int] = sc.parallelize(array)
    /*
      mapPartitionsWithIndex算子的匿名方法的参数
      index 分区的编号
      nums  整个分区的数据
     */
    val mapPartitionWithIndexRDD: RDD[(Int, Int)] = arrayRDD.mapPartitionsWithIndex((index, nums) => {
      nums.map(num => (index, num * 10))
    })
    /* foreach遍历RDD */
    mapPartitionWithIndexRDD.foreach(println(_))

  }

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

    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)

    /* 初始化SparkContext */
    val sc = new SparkContext(new SparkConf().setAppName(this.getClass.getName).setMaster("local[2]"))

    /* 调用map方法 */
    map(sc)
    /* 调用mapPartition方法 */
    mapPartition(sc)
    /* 调用mapPartitionWithIndex方法 */
    mapPartitionWithIndex(sc)
    /* 关闭资源 */
    sc.stop()
  }
}
  • flatMap(func)
    作用:类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
  • glom
    作用:将每一个分区形成一个数组,形成新的RDD类型时RDD[Array[T]]
  • groupBy(func)
    作用:分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。
  • filter(func)
    作用:过滤。返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成。
  • sample(withReplacement, fraction, seed)
    作用:以指定的随机种子随机抽样出数量为fraction的数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样,seed用于指定随机数生成器种子。
  • distinct([numTasks]))
    作用:对源RDD进行去重后返回一个新的RDD。默认情况下,只有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它。
  • coalesce(numPartitions)
    作用:缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。 coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定。默认不进行shuffle。
  • repartition(numPartitions)
    作用:根据分区数,重新通过网络随机洗牌所有数据。repartition实际上是调用的coalesce,默认是进行shuffle的,源码如下
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
  coalesce(numPartitions, shuffle = true)
}
  • sortBy(func,[ascending], [numTasks])
    作用;使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序。
  • pipe(command, [envVars])
    作用:管道,针对每个分区,都执行一个shell脚本,返回输出的RDD。

3.2.双Value类型交互

  • union(otherDataset)
    作用:并集,对源RDD和参数RDD求并集后返回一个新的RDD,不去重
  • subtract (otherDataset)
    作用:差集,计算差的一种函数,去除两个RDD中相同的元素,不同的RDD将保留下来
  • intersection(otherDataset)
    作用:交集,对源RDD和参数RDD求交集后返回一个新的RDD
  • cartesian(otherDataset)
    作用:笛卡尔积(尽量避免使用)
  • zip(otherDataset)
    作用:拉链,将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。

3.3.Key-Value类型

  • partitionBy
    作用:对pairRDD进行分区操作,如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区, 否则会生成ShuffleRDD,即会产生shuffle过程。
  • groupByKey
    作用:groupByKey也是对每个key进行操作,但只生成一个sequence。按照key进行分组,直接进行shuffle。
  • reduceByKey(func, [numTasks])
    在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]
    开发指导:reduceByKey比groupByKey效率高,建议使用。但是需要注意是否会影响业务逻辑。
  • aggregateByKey
    参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
  • 作用:在kv对的RDD中,,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出。
  • 参数描述:
    (1)zeroValue:给每一个分区中的每一个key一个初始值;
    (2)seqOp:函数用于在每一个分区中用初始值逐步迭代value;
    (3)combOp:函数用于合并每个分区中的结果。
  • foldByKey
    参数:(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
    作用:aggregateByKey的简化操作,seqop和combop相同
  • combineByKey[C]
    参数(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
    1.作用:对相同K,把V合并成一个集合。
    2.参数描述:
    (1)createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值
    (2)mergeValue: 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并
    (3)mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合并。
  • sortByKey([ascending], [numTasks])
    作用:在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
  • mapValues
    针对于(K,V)形式的类型只对V进行操作
  • join(otherDataset, [numTasks])
    作用:在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
  • cogroup(otherDataset, [numTasks])
    作用:在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD

4.Action算子

  • reduce(func)
    作用:通过func函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据。
  • collect()
    作用:在驱动程序中,以数组的形式返回数据集的所有元素。
  • count()
    作用:返回RDD中元素的个数
  • first()
    作用:返回RDD中的第一个元素
  • take(n)
    作用:返回一个由RDD的前n个元素组成的数组
  • takeOrdered(n)
    作用:返回该RDD排序后的前n个元素组成的数组
  • aggregate
  1. 参数:(zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
  2. 作用:aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致。
  • fold(num)(func)
    作用:折叠操作,aggregate的简化操作,seqop和combop一样。
  • saveAsTextFile(path)
    作用:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
  • saveAsSequenceFile(path)
    作用:将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
  • countByKey()
    作用:针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
  • foreach(func)
    作用:在数据集的每一个元素上,运行函数func进行更新
import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable

object Demo01Transformation {

  /* 测试flatMap方法 */
  def flatMap(sc: SparkContext): Unit = {
    /* 创建字符串数组 */
    val arrStr = Array("hello hadoop", "hi hive", "ha spark")
    /* 生成RDD */
    val arrStrRDD: RDD[String] = sc.parallelize(arrStr)
    /* 将RDD中的所有元素按空格切分 */
    val value: RDD[Array[String]] = arrStrRDD.map(_.split(" "))
    val array: Array[Array[String]] = value.collect()
    val buffer: mutable.Buffer[Array[String]] = array.toBuffer
    buffer.foreach(println(_))
    /* flatMap 扁平化操作 */
    val value1: RDD[String] = arrStrRDD.flatMap(_.split(" "))
    value1.foreach(println(_))
  }

  /* 测试 glom方法 */
  def glom(sc: SparkContext) = {
    /* 并行化生成RDD 指定分区 4个 */
    val numRDD: RDD[Int] = sc.parallelize(1 to 9, 4)

    val value: RDD[Array[Int]] = numRDD.glom()
    val array: Array[Array[Int]] = value.collect()
    val buffer: mutable.Buffer[Array[Int]] = array.toBuffer
    buffer.foreach(println(_))
    value.foreach(aa => println(aa.toBuffer))
  }

  /* 测试  groupBy方法 */
  def groupBy(sc: SparkContext) = {
    /* 并行化生成RDD */
    val numRDD: RDD[Int] = sc.parallelize(1 to 9)
    /* groupBy 分组  按照奇偶数分组*/
    val value: RDD[(Int, Iterable[Int])] = numRDD.groupBy(_ % 2)
    /* 遍历 */
    value.foreach(println(_))
    println("-----------------------------")

    val value1: RDD[(Int, Int)] = numRDD.map(num => (num % 2, num))
    val value2: RDD[(Int, Iterable[Int])] = value1.groupByKey()
    value2.foreach(println(_))
  }

  /* 测试  filter 方法 */
  def filter(sc: SparkContext) = {
    /* 创建字符串数组 */
    val array: Array[String] = Array("hello spark", "hi hadoop", "I want study")
    /* 并行化生成RDD */
    val arrayRDD: RDD[String] = sc.parallelize(array)
    /* 过滤掉每行数据包含 he 的数据 */
    val value: RDD[String] = arrayRDD.filter(str => !str.contains("he"))
    value.foreach(println(_))
    println("====================")
    /* 过滤掉单个包含 he 的字符 */
    arrayRDD.flatMap(_.split(" ")).filter(!_.contains("he")).foreach(println(_))
  }
  /* 测试  sample方法 取样 */
  def sample(sc: SparkContext) = {
    /* 并行化生成RDD */
    val numRDD: RDD[Int] = sc.parallelize(1 to 10)
    /* sample方法  取样  参数: */
    val value: RDD[Int] = numRDD.sample(false, 0.1)
    value.foreach(println(_))
  }

  /* 测试  distinct方法 去重 */
  def distinct(sc: SparkContext) = {
    val array = Array(1, 1, 1, 2, 9, 9, 8, 4, 7, 5, 6, 2, 3, 3, 5, 1, 7, 2)
    /* 并行化生成RDD */
    val arrayRDD: RDD[Int] = sc.parallelize(array)
    /* 指定两个分区   都去重 */
    val value: RDD[Int] = arrayRDD.distinct(2)
    value.foreach(println(_))
  }

  /* 测试  partition方法  */
  def partition(sc: SparkContext) = {
    /* 以元组的形式 创建数组 */
    val array = Array((1, 1, 9, 9, 9, 5, 5, 6, 6, 6, 7, 7), 8, 6)
    /* 并行化生成 RDD */
    val arrayRDD = sc.parallelize(array)
    /* 优化 增大分区 提高并行度 */
    println(arrayRDD.repartition(6).getNumPartitions)
    println(arrayRDD.coalesce(6).getNumPartitions)
    println(arrayRDD.coalesce(6,true).getNumPartitions)

    /* 减少分区 合并分区 */
    println(arrayRDD.repartition(1).getNumPartitions)
    println(arrayRDD.coalesce(1).getNumPartitions)
    /* 优化:尽量减少shuffle类的算子 */
  }

  /* 测试 sortBy 方法  */
  def sortBy(sc: SparkContext) = {
    val array = Array(5, 3, 4, 1, 2)
    /* 并行化生成RDD */
    val arrayRDD: RDD[Int] = sc.parallelize(array)
    /* 升序 指定分区个数为1 */
    val value: RDD[Int] = arrayRDD.sortBy(num => num, true, 1)
    value.foreach(println(_))
  }

  /* 测试 uion 方法  */
  def uion(sc: SparkContext) = {
    /* 并行化生成RDD */
    val uionRDD1: RDD[Int] = sc.parallelize(1 to 2)
    val uionRDD2: RDD[Int] = sc.parallelize(2 to 3)
    /* 合并两个RDD */
    val value: RDD[Int] = uionRDD1.union(uionRDD2)
    value.foreach(println(_))
  }

  /* 测试 subtract 方法 交集 差集 笛卡尔积 拉链 */
  def subtract(sc: SparkContext) = {
    /* 以元组的形式 创建数组 */
    val array1 = Array((1,2,3,4,5),3,5)
    val array2 = Array((3,4,5,6,7),5,6)
    /* 并行化生成RDD */
    val array1RDD = sc.parallelize(array1, 3)
    val array2RDD = sc.parallelize(array2,3)
    /* 差集 */
    val value1 = array1RDD.subtract(array2RDD)
    value1.foreach(println(_))
    println("----------------")
    /* 交集 */
    array1RDD.intersection(array2RDD).foreach(println(_))
    println("----------------")
    /* 笛卡尔积 */
    array1RDD.cartesian(array2RDD).foreach(println(_))
    println("----------------")
    /* zip 拉链 */
    array1RDD.zip(array2RDD).foreach(println(_))
  }

  /* 测试 partitionBy 方法 */
  def partitionBy(sc: SparkContext) = {
    /* 以元组的形式 创建数组 */
    val array1 = Array((1,2,3,4,5),3,5)
    /* 并行化生成RDD */
    val array1RDD: RDD[Any] = sc.parallelize(array1)
    val value: RDD[(Any, Any)] = array1RDD.map(num => (num, num))
    value.foreach(println(_))
  }

  def groupByKey(sc: SparkContext) = {

    val words = Array("one", "two", "two", "three", "three", "three")
    val wordsRdd = sc.parallelize(words)

    //    wordsRdd.count()
    wordsRdd.map((_,1))
      .groupByKey(1)  //按照key 分组  指定1个分区
      .foreach(println(_))

  }

  def reduceByKey(sc: SparkContext) = {

    val numRdd01 = sc.parallelize(Array("one", "three", "two", "two", "three",  "three"))
    val pairRdd = numRdd01.map(word => (word, 1))

    pairRdd.cache() //持久化

    pairRdd.reduceByKey(_+_)
      //      .reduceByKey((a:Int,b:Int) => a+b)
      .foreach(println(_))

    println("---------")

    //先分区内合并,然后做最后的合并   100 是每个分区的初始值
    pairRdd.aggregateByKey(100)(_+_,_+_) .foreach(println(_))

    println("---------")
    pairRdd.combineByKey(init => init+100,(a:Int,b:Int)=>a+b,(a:Int,b:Int)=>a+b) .foreach(println(_))


  }

  def foldBykey(sc: SparkContext) = {

    val numRdd = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)

    numRdd.foldByKey(100)(_+_).foreach(println(_))

  }

  def sortByKey(sc: SparkContext) = {

    val rdd = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))

    //降序 1个分区
    rdd.sortByKey(false,1)
      //取前2名
      .take(2)
      .foreach(println(_))

    println("-------")
    rdd.sortBy(_._1,false,1).foreach(println(_))

    println("-------")
    //对kv的Rdd  做value 操作
    rdd.mapValues(_*2).foreach(println(_))

  }

  def join(sc: SparkContext) = {

    val rdd01 = sc.parallelize( Array(("A",1),("B",2),("C",3)) )
    val rdd02 = sc.parallelize( Array(("A",true),("B",false)) )

    val j1Rdd: RDD[(String, (Int, Boolean))] = rdd01.join(rdd02)
    j1Rdd.foreach(println(_))

    println("---------")
    val v1: RDD[(String, (Int, Option[Boolean]))] = rdd01.leftOuterJoin(rdd02)
    v1.foreach(println(_))

    println("-------")
    v1.foreach(tuple=>{
      val k = tuple._1
      val v1 = tuple._2._1
      val value: Option[Boolean] = tuple._2._2

      println((k,v1,value.getOrElse(0)))

    })
    //cogroup
    val v2: RDD[(String, (Iterable[Int], Iterable[Boolean]))] = rdd01.cogroup(rdd02)

    v2.foreach(println(_))
  }

  def main(args: Array[String]): Unit = {
    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)

    /* 初始化SparkContext */
    val sc = new SparkContext(new SparkConf().setAppName(this.getClass.getName).setMaster("local[2]"))

    /* 调用flatMap方法 */
    //flatMap(sc)
    /* 调用glom方法 */
    //glom(sc)
    /* 调用groupBy方法 */
    //groupBy(sc)
    /* 调用filter方法 */
    //filter(sc)
    /* 调用 sample 取样 方法 */
    //sample(sc)
    /* 调用 distinct 去重 方法 */
    //distinct(sc)
    /* 调用 partition  方法 */
    //partition(sc)
    /* 调用 sortBy 排序 方法 */
    //sortBy(sc)
    /* 调用 uion  方法 */
    //uion(sc)
    /* 调用 subtract 方法 差集 交集 笛卡尔积 拉链 */
    //subtract(sc)
    /* 调用 partitionBy  方法 */
    //partitionBy(sc)
    //    groupByKey(sc)

    //    reduceByKey(sc)
    //    foldBykey(sc)
    //    sortByKey(sc)
    join(sc)
    /* 关闭资源 */
    sc.stop()
  }
}