[大数据]Spark(2)RDD(1)

1.RDD概述

1.1 IO流

在这里插入图片描述
IO流与RDD之间的关系
在这里插入图片描述

1.2 RDD

在这里插入图片描述
RDD的特性:
在这里插入图片描述
A list of partitions
多个分区,分区可以看成是数据集的基本组成单位
对于 RDD 来说, 每个分区都会被一个计算任务处理, 并决定了并行计算的粒度。
用户可以在创建 RDD 时指定 RDD 的分区数, 如果没有指定, 那么就会采用默认值。 默认值就是程序所分配到的 CPU Core 的数目.
每个分配的存储是由BlockManager 实现的, 每个分区都会被逻辑映射成 BlockManager 的一个 Block,,而这个 Block 会被一个 Task 负责计算。
A function for computing each split
计算每个切片(分区)的函数.
Spark 中 RDD 的计算是以分片为单位的,每个 RDD 都会实现compute函数以达到这个目的
A list of dependencies on other RDDs
与其他 RDD 之间的依赖关系
RDD 的每次转换都会生成一个新的 RDD, 所以 RDD 之间会形成类似于流水线一样的前后依赖关系。 在部分分区数据丢失时,Spark 可以通过这个依赖关系重新计算丢失的分区数据, 而不是对 RDD 的所有分区进行重新计算
Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
对存储键值对的 RDD,还有一个可选的分区器
只有对于 key-value的 RDD,才会有 Partitioner, 非key-value的 RDD 的 Partitioner 的值是 None;Partitiner 不但决定了 RDD 的本区数量, 也决定了 parent RDD Shuffle 输出时的分区数量
Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)
存储每个切片优先(preferred location)位置的列表
比如对于一个 HDFS 文件来说, 这个列表保存的就是每个 Partition 所在文件块的位置. 按照“移动数据不如移动计算”的理念, Spark 在进行任务调度的时候, 会尽可能地将计算任务分配到其所要处理数据块的存储位置.

2.RDD编程

2.1 编程模型

在这里插入图片描述
算子:从认知心理学角度来讲,解决问题其实是将问题的初始状态,通过一系列的转换操作(operator),变成解决状态。

2.2 RDD的创建

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

2.2.1 从集合中创建RDD

从集合中创建RDD,Spark主要提供了两种函数:parallelize和makeRDD

object Spark01_CreateRDD_mem {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_CreateRDD_mem").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)
    //创建一个集合对象
    val list: List[Int] = List(1, 2, 3, 4)
    //根据集合创建RDD 方式1
    val rdd1: RDD[Int] = sc.parallelize(list)
    //根据集合创建RDD 方式2
    val rdd2: RDD[Int] = sc.makeRDD(list)
    //打印输出
    rdd1.collect().foreach(println)
    rdd2.collect().foreach(println)
    //关闭连接
    sc.stop()
  }
}

2.2.2 从外部存储系统的数据集创建RDD

由外部存储系统的数据集创建RDD包括:本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、HBase等
1)数据准备
分别在本地路径与hdfs上准本需要读取的文件
2)创建RDD

object Spark02_CreateRDD_file {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark02_CreateRDD_file").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)
    //从本地文件中读取创建RDD
    val rdd1: RDD[String] = sc.textFile("E:\\spark-0701\\input\\1.txt")
    //从hdfs服务器中读数据创建RDD
    val rdd2: RDD[String] = sc.textFile("hdfs://hadoop102:8020/input")
    rdd1.collect().foreach(println)
    rdd2.collect().foreach(println)
    //关闭连接
    sc.stop()
  }
}

2.3 分区规则

2.3.1 默认分区方式

从集合中创建RDD,取决于分配给应用的CPU核数,即setMaster中
从外部文件中创建RDD,取决于min(分配给应用的CPU核数,2)

object Spark03_Partition_default {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark03_Partition_default").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)
    //通过集合创建RDD
    val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    //通过外部环境创建RDD
    val rdd2: RDD[String] = sc.textFile("E:\\spark-0701\\input")
    println(rdd1.partitions.size)
    println(rdd2.partitions.size)

    //关闭连接
    sc.stop()
  }
}

输出:

12
2

即通过集合的方式创建了12个分区,通过外部环境的方式创建了2个分区

跟踪源码(集合):
在这里插入图片描述

2.3.2 从集合中创建RDD(自定义分区)

object Spark04_Partition_mem {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark04_Partition_mem").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)
    //通过集合创建RDD

    //默认分区数,集合中4个数据,实际输出12个分区,分区中数据分布:2分区->1,5分区->2,8分区->3,11分区->4
    val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    rdd1.saveAsTextFile("E:\\spark-0701\\output1")

    //设置4个分区,集合中4个数据,实际输出4个分区,分区中数据分布:0分区->1,1分区->2,2分区->3,3分区->4
    val rdd2: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),numSlices = 4)
    rdd2.saveAsTextFile("E:\\spark-0701\\output2")

    //设置3个分区,集合中4个数据,实际输出3个分区,分区中数据分布:0分区->1,1分区->2,2分区->3,4
    val rdd3: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),numSlices = 3)
    rdd3.saveAsTextFile("E:\\spark-0701\\output3")
    //关闭连接
    sc.stop()
  }
}

分区源码(分区规则):
在这里插入图片描述

2.3.3 通过外部文件创建RDD

1.在textFile方法中,第二个参数minPartitions表示最小分区数,不是实际分区数。
2.在实际计算分区个数的时候,会根据文件的总大小和最小分区数进行相除运算,如果余数为0,那么最小分区数为最终实际的分区数,如果余数不为0,那么实际的分区数,需要计算。

object Spark05_Partition_file {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_CreateRDD_mem").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)
    //从本地文件中读取数据,创建RDD
    //输入数据1 2 3 4,(每行一个)采用默认分区方式,最终分区数:2  0分区->1,2; 1分区->3,4
    val rdd1: RDD[String] = sc.textFile("E:\\spark-0701\\input\\2.txt")
    rdd1.saveAsTextFile("E:\\spark-0701\\output1")

    //输入数据1 2 3 4(每行一个),最小分区数设置为3,最终分区数:4  0分区->1,2; 1分区->3; 2分区->4; 3分区:空
    val rdd2: RDD[String] = sc.textFile("E:\\spark-0701\\input\\2.txt", minPartitions = 3)
    rdd2.saveAsTextFile("E:\\spark-0701\\output2")

    //输入数据1 2 3 4 5 6(共一行),最小分区数设置为3,最终分区数:3  0分区->1,2,3,4,5,6; 1分区:空; 2分区:空;
    val rdd3: RDD[String] = sc.textFile("E:\\spark-0701\\input\\3.txt", minPartitions = 3)
    rdd3.saveAsTextFile("E:\\spark-0701\\output3")
    //关闭连接
    sc.stop()
  }
}

分区源码:

public InputSplit[] getSplits(JobConf job, int numSplits)
    throws IOException {
    FileStatus[] files = listStatus(job);
    
    // Save the number of input files for metrics/loadgen
    job.setLong(NUM_INPUT_FILES, files.length);
    long totalSize = 0;                           // compute total size
    for (FileStatus file: files) {                // check we have valid files
      if (file.isDirectory()) {
        throw new IOException("Not a file: "+ file.getPath());//如果是目录则抛异常
      }
      totalSize += file.getLen();//累加文件总大小
    }

    long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);//预计每一个切片中放多少字节
    long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
      FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);//每个分区中放的最小值

    // generate splits
    ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);//生成切片规划信息
    NetworkTopology clusterMap = new NetworkTopology();
    for (FileStatus file: files) {//遍历文件
      Path path = file.getPath();
      long length = file.getLen();
      if (length != 0) {
        FileSystem fs = path.getFileSystem(job);
        BlockLocation[] blkLocations;
        if (file instanceof LocatedFileStatus) {//文件信息
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
          blkLocations = fs.getFileBlockLocations(file, 0, length);
        }
        if (isSplitable(fs, path)) {//如果文件时可切片的
          long blockSize = file.getBlockSize();//默认块大小
          long splitSize = computeSplitSize(goalSize, minSize, blockSize);//计算切片大小
          long bytesRemaining = length;//等待处理的大小
          while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {//除下来大于1.1,继续切,否则不切了
            String[] splitHosts = getSplitHosts(blkLocations,
                length-bytesRemaining, splitSize, clusterMap);//切片的信息
            splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                splitHosts));//存入集合之中
            bytesRemaining -= splitSize;//剩余待处理的切片
          }

          if (bytesRemaining != 0) {//还有剩余再添加一个切片
            String[] splitHosts = getSplitHosts(blkLocations, length
                - bytesRemaining, bytesRemaining, clusterMap);
            splits.add(makeSplit(path, length - bytesRemaining, bytesRemaining,
                splitHosts));
          }
        } else {
          String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);
          splits.add(makeSplit(path, 0, length, splitHosts));
        }
      } else { 
        //Create empty hosts array for zero length files
        splits.add(makeSplit(path, 0, length, new String[0]));
      }
    }
    LOG.debug("Total # of splits: " + splits.size());
    return splits.toArray(new FileSplit[splits.size()]);
  }

  protected long computeSplitSize(long goalSize, long minSize,
                                       long blockSize) {
    return Math.max(minSize, Math.min(goalSize, blockSize));
  }

在这里插入图片描述

2.4 Tranformation转换算子

RDD整体上分为Value类型、双Value类型和Key-Value类型

2.4.1 Value类型

2.4.1.1 map映射

map算子:
在这里插入图片描述

2.4.1.2 mapPartitions() 以分区为单位执行map

MapPartitions算子
在这里插入图片描述
代码示例:

/**
 * 转换算子mapPartitions
 */
object Spark01_Transformation_mapPartitions {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_Transformation_mapPartitions").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

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

    //以分区为单位对RDD中的元素进行映射
    //如果每一个元素创建一个连接,效率很低,可以对每个分区的元素,创建一个连接
    val newRdd: RDD[Int] = rdd.mapPartitions(datas => {
      datas.map(elem => elem * 2)
    })

    //打印输出
    newRdd.collect().foreach(println)

    //关闭连接
    sc.stop()
  }
}
2.4.1.3 map()和mapPartitions()的区别

在这里插入图片描述

2.4.1.4 mapPartitionsWithIndex() 带分区号

mapPartitionsWithIndex算子:
在这里插入图片描述

/**
 * 以分区为单位对RDD中的元素进行映射,并且带分区编号
 */
object Spark02_Transformation_mapPartitionsWithIndex {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_CreateRDD_mem").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

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

    //第二个分区数据*2,其余分区数据保持不变

    val newRDD: RDD[Int] = rdd.mapPartitionsWithIndex(
      (index, datas) => {
        index match {
          case 1 => datas.map(_ * 2)
          case _ => datas
        }
      }
    )

    //打印输出
    newRDD.collect().foreach(println)

    //关闭连接
    sc.stop()
  }
}

输出

1 2 6 8 10 6 7 8
2.4.1.5 flatMap() 压平

flatMap算子:
在这里插入图片描述
代码示例:

object Spark03_Transformation_flatMap {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark03_Transformation_flatMap").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

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

    //扁平化处理,如果匿名函数输入和输出相同,那么不能简化
    val newRDD: RDD[Int] = rdd.flatMap({
      datas => datas
    })
    
    newRDD.collect().foreach(println)
    //关闭连接
    sc.stop()
  }
}
2.4.1.6 glom() 分区转换数组

glom算子:
在这里插入图片描述
代码示例:

object Spark04_Transformation_glom {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark04_Transformation_glom").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

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

    println("没有glom之前")
    rdd.mapPartitionsWithIndex({
      (index, datas) => {
        print(index + "--->" + datas.mkString(","))
        datas
      }
    }).collect()

    println("调用glom之后")
    val newRDD: RDD[Array[Int]] = rdd.glom()
    newRDD.mapPartitionsWithIndex({
      (index, datas) => {
        print(index + "--->" + datas.next().mkString(","))
        datas
      }
    }).collect()

    //关闭连接
    sc.stop()
  }
}

输出

没有glom之前
 0--->1,2,3
 1--->4,5,6
 调用glom之后
1--->4,5,6
0--->1,2,3
2.4.1.7 groupBy() 分组

groupBy算子:
在这里插入图片描述
代码示例

/**
 * 按照指定的规则对RDD中的元素进行分组
 */
object Spark05_Transformation_groupBy {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark05_Transformation_groupBy").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 3)

    println("groupBy分组前")
    rdd.mapPartitionsWithIndex(
      (index, datas) => {
        println(index + "--->" + datas.mkString(""))
        datas
      }
    ).collect()
    
    println("groupBy分组后")
    val newRDD: RDD[(Int, Iterable[Int])] = rdd.groupBy(_ % 2)
    newRDD.mapPartitionsWithIndex(
      (index, datas) => {
        println(index + "--->" + datas.mkString(""))
        datas
      }
    ).collect()
    //关闭连接
    sc.stop()
  }
}

输出:

groupBy分组前
1--->456
0--->123
2--->789
groupBy分组后
2--->
1--->(1,CompactBuffer(1, 3, 5, 7, 9))
0--->(0,CompactBuffer(2, 4, 6, 8))

groupBy会存在shuffle过程
shuffle:将不同的分区数据进行打乱重组的过程
shuffle一定会落盘。可以在local模式下执行程序,通过4040看效果。

2.4.1.8 GroupBy之WordCount

在这里插入图片描述
代码示例:

/**
 * 使用GroupBY来完成wordCount
 */
object Spark06_WordCount {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark06_WordCount").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

 
    //实现方式1
    val rdd: RDD[String] = sc.makeRDD(List("Hello Scala", "Hello Spark", "Hello World"))//创建RDD
    val flatMapRDD: RDD[String] = rdd.flatMap(_.split(" ")) //扁平化映射
    val mapRDD: RDD[(String, Int)] = flatMapRDD.map((_, 1)) //结构转换,计数
    val groupByRDD: RDD[(String, Iterable[(String, Int)])] = mapRDD.groupBy(_._1) //按照key对RDD中的元素进行分组
    //对分组后的元素再次进行映射
    val resRDD: RDD[(String, Int)] = groupByRDD.map {
      case (word, datas) => {
        (word, datas.size)
      }
    }
    resRDD.collect().foreach(println)

    //实现方式2
    val rdd: RDD[String] = sc.makeRDD(List("Hello Scala", "Hello Spark", "Hello World"))//创建RDD
    val flatMapRDD: RDD[String] = rdd.flatMap(_.split(" "))
    val groupByRDD: RDD[(String, Iterable[String])] = flatMapRDD.groupBy(word => word) //将RDD中的单词进行分组
    val resRDD: RDD[(String, Int)] = groupByRDD.map {
      case (word, datas) => {
        (word, datas.size)
      }
    }
    resRDD.collect.foreach(println)
    
    //实现方式3(复杂RDD创建)
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("Hello Scala", 2), ("Hello Spark", 3), ("Hello World", 2))) //创建RDD
    val rdd1: RDD[String] = rdd.map {
      case (str, count) => {
        (str + " ") * count
      }
    }
    val flatMapRDD: RDD[String] = rdd1.flatMap(_.split(" "))
    val groupByRDD: RDD[(String, Iterable[String])] = flatMapRDD.groupBy(word => word)
    val resRDD: RDD[(String, Int)] = groupByRDD.map {
      case (word, datas) => {
        (word, datas.size)
      }
    }
    resRDD.collect().foreach(println)
  
    //实现方式4(复杂RDD创建) ("Hello Scala", 2) => (Hello, 2), (scala, 2)
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("Hello Scala", 2), ("Hello Spark", 3), ("Hello World", 2))) //创建RDD
    val flatMapRDD: RDD[(String, Int)] = rdd.flatMap {
      case (words, count) => {
        //对多个单词组成的字符串进行切割
        words.split(" ").map(words => (words, count))
      }
    }
    val groupByRDD: RDD[(String, Iterable[(String, Int)])] = flatMapRDD.groupBy(_._1) //按单词分组
    val resRDD: RDD[(String, Int)] = groupByRDD.map {
      case (word, datas) => {
        (word, datas.map(_._2).sum)
      }
    } //对RDD元素进行重新映射

    resRDD.collect().foreach(println)//输出
    //关闭连接
    sc.stop()
  }
}
2.4.1.9 filter() 过滤

filter算子:
在这里插入图片描述
代码示例:

object Spark07_Transformation_filter {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark07_Transformation_filter ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4), 2) //创建RDD
    val filterRDD: RDD[Int] = rdd.filter(_ % 2 == 0)//筛选出2的倍数
    filterRDD.collect().foreach(println)

    //关闭连接
    sc.stop()
  }
}

输出

2
4
2.4.1.10 sample() 采样

sample算子:
在这里插入图片描述
代码示例:

object Spark08_Transformation_sample {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark08_Transformation_sample ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    val rdd: RDD[Int] = sc.makeRDD(1 to 10) //创建RDD
    //从rdd中随机抽取一组数据(抽样放回)
    rdd.sample(true, 0.4, 2).collect().foreach(println)
    //从rdd中随机抽取一组数据(抽样不放回)
    rdd.sample(false, 0.2, 3).collect().foreach(println)
    //关闭连接
    sc.stop()
  }
}

输出:

3 4 4 5 6 8
9
2.4.1.11 distinct() 去重

distinct算子:
在这里插入图片描述
代码示例:

object Spark09_Transformation_distinct {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark09_Transformation_distinct").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    val numRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 5, 4, 3, 3), 5)
    //输出当前RDD中的分区个数
    numRDD.mapPartitionsWithIndex {
      (index, datas) => {
        print(index + "-->" + datas.mkString(","))
        datas
      }
    }.collect()
    //对RDD中的数据进行去重
    println("---------------------")
    val newRDD: RDD[Int] = numRDD.distinct()
    newRDD.mapPartitionsWithIndex {
      (index, datas) => {
        print(index + "-->" + datas.mkString(","))
        datas
      }
    }.collect()
    //关闭连接
    sc.stop()
  }
}

输出:

4-->3,3 
2-->4,5 
3-->5,4 
0-->1 
1-->2,3
---------------------
1-->1
2-->2
0-->5
4-->4
3-->3
2.4.1.12 coalesce() 重新分区

Coalesce算子包括:配置执行Shuffle和配置不执行Shuffle两种方式。

Coalesce算子:
在这里插入图片描述
代码示例:

object Spark10_Transformation_coalesce {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark10_Transformation_coalesce ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    val numRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 3) //创建RDD
    //查看分区情况
    numRDD.mapPartitionsWithIndex {
      (index, datas) => {
        print(index + "-->" + datas.mkString(","))
        datas
      }
    }.collect()
    println("-------------------------")
    //缩减分区
    val newRDD: RDD[Int] = numRDD.coalesce(2)
    //查看分区情况
    newRDD.mapPartitionsWithIndex {
      (index, datas) => {
        print(index + "-->" + datas.mkString(","))
        datas
      }
    }.collect()
    //关闭连接
    sc.stop()
  }
}

输出:

# 原始数据
1-->3,4
0-->1,2
2-->5,6
-------------------------
# coalesce
0-->1,2
1-->3,4,5,6
-------------------------
# repartition
1-->
2-->
0-->2,4,6
3-->1,3,5
-------------------------
# coalesec(执行shuffle)
1-->2,4,6
0-->1,3,5
2.4.1.15 sortBy() 排序

sortBy算子:
在这里插入图片描述
代码示例:

object Spark11_Transformation_sortBy {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_CreateRDD_mem").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val numRDD: RDD[Int] = sc.makeRDD(List(1, 4, 3, 2))
    val strRDD: RDD[String] = sc.makeRDD(List("1", "4", "3", "22"))

    //升序排序
    val sortedRDD1: RDD[Int] = numRDD.sortBy(num => num)
    //降序排序
    val sortedRDD2: RDD[Int] = numRDD.sortBy(num => num, false)
    //字符串排序(按字符字典顺序)
    val sortedRDD3: RDD[String] = strRDD.sortBy(elem => elem)
    //字符串排序(转换为整数后排序)
    val sortedRDD4: RDD[String] = strRDD.sortBy(elem => elem.toInt)

    //打印输出
    println("---------------------")
    sortedRDD1.collect().foreach(println)
    println("---------------------")
    sortedRDD2.collect().foreach(println)
    println("---------------------")
    sortedRDD3.collect().foreach(println)
    println("---------------------")
    sortedRDD4.collect().foreach(println)
    //关闭连接
    sc.stop()
  }

输出:

---------------------
1 2 3 4
---------------------
4 3 2 1
---------------------
1 22 3 4
---------------------
1 3 4 22
2.4.1.16 pipe() 调用脚本

pipe算子:
在这里插入图片描述

2.4.2 双Value类型交互

Union算子:
在这里插入图片描述
subtract算子:
在这里插入图片描述
intersection算子:
在这里插入图片描述
zip算子:
在这里插入图片描述

代码示例:

/**
 * 转换算子-双Value类型
 * 并集 union
 * 交集 intersect
 * 差集 diff-->subtract
 * 拉链 zip
 */
object Spark12_Transformation_doubleValue {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark12_Transformation_doubleValue").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    val rdd2: RDD[Int] = sc.makeRDD(List(4, 5, 6, 7))

    //并集
    val unionRDD: RDD[Int] = rdd1.union(rdd2)
    //交集
    val intersectRDD: RDD[Int] = rdd1.intersection(rdd2)
    //差集
    val subtractRDD: RDD[Int] = rdd1.subtract(rdd2)
    //拉链
    val zipRDD: RDD[(Int, Int)] = rdd1.zip(rdd2)

    //打印输出
    unionRDD.collect().foreach(println)
    println("---------------------")
    intersectRDD.collect().foreach(println)
    println("---------------------")
    subtractRDD.collect().foreach(println)
    println("---------------------")
    zipRDD.collect().foreach(println)

    //关闭连接
    sc.stop()
  }
}

输出:

1 2 3 4 4 5 6 7
---------------------
4
---------------------
1 2 3
---------------------
(1,4) (2,5) (3,6) (4,7)

2.4.3 Key-Value类型

2.4.3.1 partitionBy() 按照K重新分区

partitionBy算子:
在这里插入图片描述
代码示例:HashPartitioner

/**
 * partitionBy算子
 * 对KV类型的RDD按照key进行重新分区
 */
object Spark01_Transformation_partitionBy {

  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_Transformation_partitionBy").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    //RDD本身没有partitionBy方法,通过隐式转换动态给kv类型的RDD扩展的功能
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3)
    rdd.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "---->" + datas.mkString(","))
        datas
      }
    }.collect()

    println("-----------------------")
    //执行重新分区(Hash)
    val newRDD1: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(2))
    newRDD1.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "---->" + datas.mkString(","))
        datas
      }
    }.collect()

    println("-----------------------")
    //执行重新分区(自定义)
    val newRDD2: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(2))
    newRDD2.mapPartitionsWithIndex {
      (index, datas) => {
        println(index + "---->" + datas.mkString(","))
        datas
      }
    }.collect()
    //关闭连接
    sc.stop()
  }

}

//自定义分区器
class MyPartitioner(partitions: Int) extends Partitioner {
  /**
   * 获取分区个数
   *
   * @return
   */
  override def numPartitions: Int = partitions

  /**
   * 指定分区规则 返回值Int表示分区编号,从0开始
   *
   * @param key
   * @return
   */
  override def getPartition(key: Any): Int = {
    if (key.isInstanceOf[Int]) {
      val keyInt: Int = key.asInstanceOf[Int]
      if (keyInt % 2 == 0) {
        0
      } else {
        1
      }
    } else {
      0
    }
  }
}



输出:

0---->(1,aaa)
1---->(2,bbb)
2---->(3,ccc)
-----------------------
0---->(2,bbb)
1---->(1,aaa),(3,ccc)
-----------------------
0---->(2,bbb)
1---->(1,aaa),(3,ccc)


2.4.3.2 reduceByKey() 按照K聚合V

reduceByKey算子:

在这里插入图片描述
代码示例:

object Spark02_Transformation_reduceByKey {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark02_Transformation_reduceByKey").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 5), ("b", 2)))

    //按照key进行聚合
    val resRDD: RDD[(String, Int)] = rdd.reduceByKey(_ + _)

    //打印输出
    resRDD.collect().foreach(println)
    //关闭连接
    sc.stop()
  }

}

输出:

(a,6)
(b,7)
2.4.3.3 groupByKey() 按照K重新分组

groupByKey算子:
在这里插入图片描述
代码示例:

/**
 * groupByKey算子:根据key对RDD中的元素进行分组
 */
object Spark03_Transformation_groupByKey {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark01_CreateRDD_mem").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd = sc.makeRDD(List(("a", 1), ("b", 5), ("a", 5), ("b", 2)))

    //根据key对RDD进行分组
    val groupRDD: RDD[(String, Iterable[Int])] = rdd.groupByKey()
    val resRDD: RDD[(String, Int)] = groupRDD.map {
      case (key, datas) => {
        (key, datas.sum)
      }
    }
    resRDD.collect().foreach(println)
    sc.stop()
  }

}

输出:

(a,6)
(b,7)
2.4.3.4 reduceByKey和groupByKey区别

1)reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。
2)groupByKey:按照key进行分组,直接进行shuffle。
3)开发指导:在不影响业务逻辑的前提下,优先选用reduceByKey。求和操作不影响业务逻辑,求平均值影响业务逻辑。

2.4.3.5 aggregateByKey() 按照K处理分区内和分区间逻辑

aggregateByKey算子:
在这里插入图片描述

代码示例:

/**
 * aggeregate算子:按照key对分区内以及分区间的数据进行处理
 * aggregateByKey(初始值)(分区内计算规则,分区间计算规则)
 */
object Spark04_Transformation_aggregateByKey {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark04_Transformation_aggregateByKey").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)

    //分区之前每个分区中的情况
    rdd.mapPartitionsWithIndex(
      (index, datas) => {
        println(index + "---->" + datas.mkString(","))
        datas
      }
    ).collect()
    println("---------------------")

    //aggregateByKey实现wordCount
    rdd.aggregateByKey(0)(_ + _, _ + _).collect().foreach(println)
    println("---------------------")

    //aggregateByKey实现分区最大值求和
    rdd.aggregateByKey(0)(
      (x, y) => math.max(x, y), //分区内求最大值
      (a, b) => a + b //分区间求和
    ).collect().foreach(println)

    //关闭连接
    sc.stop()
  }

}

输出:

1---->(b,3),(c,6),(c,8)
0---->(a,3),(a,2),(c,4)
---------------------
(b,3)
(a,5)
(c,18)
---------------------
(b,3)
(a,3)
(c,12) #4+8
2.4.3.6 foldByKey() 分区内和分区间相同的aggregateByKey()

foldByKey算子:
在这里插入图片描述
与reduceByKey的区别在于是否需要指定初始值

代码示例:

object Spark05_Transformation_foldByKey {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark05_Transformation_foldByKey").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)

    //使用foldByKey计算
    val resRDD: RDD[(String, Int)] = rdd.foldByKey(0)(_ + _)

    //打印输出
    resRDD.collect().foreach(println)
    
    //关闭连接
    sc.stop()
  }
}

输出:

(b,3)
(a,5)
(c,18)
2.4.3.7 combineByKey()转换结构后分区内和分区间操作

combineByKey算子:
在这里插入图片描述
案例分析:
在这里插入图片描述

代码示例:

输出:

object Spark06_Transformation_combineByKey {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark06_Transformation_combineByKey ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
    val input: RDD[(String, Int)] = sc.makeRDD(list, 2)

    //将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组
    val combineRdd: RDD[(String, (Int, Int))] = input.combineByKey(
      (_, 1), //初始值
      (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1), //分区内
      (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2) //分区间
    )
    
    //合并后的结果
    combineRdd.collect().foreach(println)
    print("----------------")
    
    //计算平均值
    combineRdd.map {
      case (key, value) => {
        (key, value._1 / value._2.toDouble)
      }
    }.collect().foreach(println)

    //关闭连接
    sc.stop()
  }

}

(b,(286,3))
(a,(274,3))
----------------
(b,95.33333333333333)
(a,91.33333333333333)
2.4.3.8 reduceByKey、aggregateByKey、foldByKey、combineByKey

在这里插入图片描述

2.4.3.9 sortByKey() 按照K进行排序

sortByKey算子:
在这里插入图片描述
代码示例:

object Spark07_Transformation_sortByKey {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark07_Transformation_sortByKey ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((3, "aa"), (6, "cc"), (2, "bb"), (1, "dd")))

    //按照key对元素进行排序(默认升序)
    rdd.sortByKey().collect().foreach(println)
    //关闭连接
    sc.stop()
  }

}

输出:

(1,dd)
(2,bb)
(3,aa)
(6,cc)
2.4.3.9 mapValue() 只对V进行操作

mapValues算子:
在这里插入图片描述
代码示例:

object Spark08_Transformation_mapValue {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark08_Transformation_mapValue ").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "d"), (2, "b"), (3, "c")))

    //改变value的值,加上"|||"字符串
    rdd.mapValues("|||" + _).collect().foreach(println)
    
    //关闭连接
    sc.stop()

  }
}

输出:

(1,|||a)
(1,|||d)
(2,|||b)
(3,|||c)
2.4.3.10 join()连接 将相同的key对应的多个value关联在一起

join算子:
相当于内连接,将两个RDD中的key相同的数据匹配,如果key匹配不上,那么数据不关联。
在这里插入图片描述

2.4.3.11 cogroup() 类似全连接,但是在同一个RDD中对key聚合

操作两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。
cogroup算子:
在这里插入图片描述
代码示例:

object Spark09_Transformation_join {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark09_Transformation_join").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)
    //创建RDD
    val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))
    val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6)))

    //join
    rdd.join(rdd1).collect().foreach(println)
    println("-------------------")

    //cogroup
    rdd.cogroup(rdd1).collect().foreach(println)
    //关闭连接
    sc.stop()
  }

}

输出:

(1,(a,4))
(2,(b,5))
-------------------
(1,(CompactBuffer(a),CompactBuffer(4)))
(2,(CompactBuffer(b),CompactBuffer(5)))
(3,(CompactBuffer(c),CompactBuffer()))
(4,(CompactBuffer(),CompactBuffer(6)))

2.4.4 实例(广告点击Top3)

在这里插入图片描述
代码实现:

/**
 * 统计每个省份广告点击排名前三的
 */
object Spark10_TopN {
  def main(args: Array[String]): Unit = {
    //创建Spark配置文件对象
    val conf: SparkConf = new SparkConf().setAppName("Spark10_TopN").setMaster("local[*]")
    //创建SparkContext
    val sc: SparkContext = new SparkContext(conf)

    //1.读取外部文件,创建RDD  {时间戳,省份id,城市id,用户id,广告id}
    val logRDD: RDD[String] = sc.textFile("E:\\spark-0701\\input\\agent.log")

    //2.对读取到的数据,进行结构转换 (省份id-广告id, 1)
    val mapRDD: RDD[(String, Int)] = logRDD.map {
      line => {
        //2.1 用空格对读取的一行字符串进行切分
        val fields: Array[String] = line.split(" ")
        //2.2 封装为元组结构返回
        (fields(1) + "-" + fields(4), 1)
      }
    }

    //3.对当前省份每一个广告点击次数进行聚合 (省份A-广告A,1000) (省份A-广告B,800)
    val reduceRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_ + _)

    //4.再次对结构进行转换,将省份作为key (省份A,(广告A,1000)),(省份B,(广告B,800))
    val map1RDD: RDD[(String, (String, Int))] = reduceRDD.map {
      case (proAndAd, clickCount) => {
        val proAndAdArr: Array[String] = proAndAd.split("-")
        (proAndAdArr(0), (proAndAdArr(1), clickCount))
      }
    }

    //5.按照省份对数据进行分组  (省份 Iterable[(广告A,80),(广告B,100)...(广告D,200)])
    val groupRDD: RDD[(String, Iterable[(String, Int)])] = map1RDD.groupByKey()

    //6.对每一个省份中的广告点击此次数进行降序排序并选前三名
    val resRDD: RDD[(String, List[(String, Int)])] = groupRDD.mapValues {
      itr => {
        itr.toList.sortWith {
          (left, right) => {
            left._2 > right._2
          }
        }.take(3)
      }
    }

    //7.打印输出
    resRDD.collect().foreach(println)

    //关闭连接
    sc.stop()
  }

}

输出:

(8,List((2,27), (20,23), (11,22)))
(4,List((12,25), (16,22), (2,22)))
(6,List((16,23), (24,21), (27,20)))
(0,List((2,29), (24,25), (26,24)))
(2,List((6,24), (21,23), (29,20)))
(7,List((16,26), (26,25), (1,23)))
(5,List((14,26), (21,21), (12,21)))
(9,List((1,31), (28,21), (0,20)))
(3,List((14,28), (28,27), (22,25)))
(1,List((3,25), (6,23), (5,22)))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值