Spark 算子

1、转换算子

1.1 map

    val sc = SparkContext.getOrCreate(new SparkConf().setMaster("local[2]").setAppName(this.getClass.getSimpleName))
    val a1 = sc.makeRDD(List(2, 4, 5, 7, 8, 9), 2)
    a1.map(x => {
      println(">>>>>>>>>>>")
      x * 2
    }).collect.foreach(println(_))

1.2 mapPartitions

​ map和mapPartition的区别:map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。是串行,效率低
​ mapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据。并行,效率高,但消耗内存,容易造成内存溢出

    // 1.每个分区计算一次的测试
    println("每个分区计算一次")
    a1.mapPartitions(x => {
      println(">>>>>>>>>>>")
      x.map(_ * 2)
    }).foreach(println)
    //2.返回每个分区的最大值
    println("返回每个分区的最大值")
    sc.makeRDD(List(1, 2, 3, 4)).mapPartitions(x => {
      List(x.max).toIterator
    }).collect.foreach(println)

    //3、将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
    println("过滤数据")
    a1.mapPartitions(x => x.filter(_ == 2)).foreach(println(_))

image-20210123231943767

1.3 mapPartitionsWithIndex

​ 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
​ 在使用中,前一个值为分区号,后一个值为分区的迭代器

    //只保留分区号为1的分区数据
    println("只保留分区号为1的分区数据")
    sc.makeRDD(List(1, 2, 3, 4), 2).mapPartitionsWithIndex((x, y) => {
      if (x == 1) {
        y
      } else {
        Nil.toIterator
      }
    }).collect.foreach(println)
    //输出对应分区号
    println("输出对应分区号")
    a1.mapPartitionsWithIndex({
      case (x, y) =>
        y.map((_, "分区号为:" + x))
    }).foreach(println(_))
    println()

image-20210123231953639

1.4 flatMap

将处理的数据进行扁平化后再进行映射处理

    println("扁平映射1")
    sc.makeRDD(List(
      List(1, 2), List(3, 4)
    )).flatMap(x => x.map((_, 1))).collect.foreach(println)
    println("扁平映射2")
    sc.makeRDD(List("aaa bbb", "ccc ddd", "ccc eee")).flatMap(x => {
      x.split(" ")
    }).map((_, 1)).reduceByKey(_ + _).foreach(println(_))

image-20210123232002302

1.5 glom

将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变

    println("同分区的所有数据转为数组")
    sc.makeRDD(List(1, 2, 3, 4, "a"), 2).glom().collect.foreach(x => {
      println(x.mkString(","))
    })
    println("计算所有分区最大值求和(分区内取最大值,分区间最大值求和)")
    val max = sc.makeRDD(List(1, 2, 3, 4), 2).glom().map(x => x.max).collect.sum
    println(max)

image-20210123232022844

1.6 groupBy

​ 将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中一个组的数据在一个分区中,但是并不是说一个分区中只有一个组

    println("根据首字母进行分组")
    sc.makeRDD(List("hello", "hadoop", "spark", "hive")).groupBy(_.charAt(0)).collect.foreach(println)
    println("根据时段进行分组")
    sc.makeRDD(List("81.220.24.207 - - 17/05/2015:10:05:21 +0000 GET /favicon.ico"
      , "66.249.73.135 - - 17/05/2015:11:05:17 +0000 GET /blog/geekery/vmware-cpu-performance.html"
      , "46.105.14.53 - - 17/05/2015:11:05:42 +0000 GET /blog/tags/puppet?flav=rss20"
      , "218.30.103.62 - - 17/05/2015:11:05:11 +0000 GET /robots.txt")).map(x => {
      val datas = x.split(" ")
      val time = datas(3)
      val sdf = new SimpleDateFormat("dd/MM/yy:HH:mm:ss")
      val date = sdf.parse(time)
      val sdf2 = new SimpleDateFormat("HH")
      val hour = sdf2.format(date)
      (hour, 1)
    }).groupBy(_._1).map {
      case (hour, iter) => (hour, iter.size)
    }.collect.foreach(println(_))
    //      .mapValues(_.size).foreach(println(_))

image-20210123232038715

1.7 filter

​ 将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
​ 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。

    sc.makeRDD(List("81.220.24.207 - - 17/04/2015:10:05:21 +0000 GET /favicon.ico"
      , "66.249.73.135 - - 17/05/2015:11:05:17 +0000 GET /blog/geekery/vmware-cpu-performance.html"
      , "46.105.14.53 - - 17/05/2015:11:05:42 +0000 GET /blog/tags/puppet?flav=rss20"
      , "218.30.103.62 - - 17/05/2015:11:05:11 +0000 GET /robots.txt")).filter(x => {
      val datas = x.split(" ")
      val time = datas(3)
      time.startsWith("17/05/2015")
    }).collect.foreach(println(_))

image-20210123232057219

1.8 sample

根据指定的规则从数据集中抽取数据

1.8.1 抽取数据不放回(伯努利算法)
  • 伯努利算法:又叫 0、1 分布。例如扔硬币,要么正面,要么反面。
    具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不要
  • 第一个参数:抽取的数据是否放回,false:不放回
  • 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取;
  • 第三个参数:随机数种子,如果不写该参数,那么使用的是当前系统时间作为随机数种子
1.8.2 抽取数据放回(泊松算法)
  • 第一个参数:抽取的数据是否放回,true:放回;false:不放回
  • 第二个参数:重复数据的几率,范围大于等于 0.表示每一个元素被期望抽取到的次数
  • 第三个参数:随机数种子,如果不写该参数,那么使用的是当前系统时间作为随机数种子
    val dataRDD = sc.makeRDD(List(
      1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    ), 1)
	//抽取数据不放回
    dataRDD.sample(false, 0.5).foreach(x => print(x + "\t"))
    println()
	//抽取数据放回
    dataRDD.sample(true, 2).foreach(x => print(x + "\t"))
    println()

image-20210123232107972

1.9 distinct

将数据集中重复的数据去重

  • distinctList底层是个HashSet

  • distinctRDD底层方法如下

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

    val str = sc.makeRDD(List(1, 2, 3, 4, 1, 2, 3, 4)).distinct.collect.mkString(",")
    println(str)

image-20210123232117487

1.10 coalesce

根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本
coalesce方法默认不会打乱分区重新组合,容易导致数据倾斜
但coalease还有第二个参数,默认为false,不shuffle,设置为true即可进行shuffle让数据变的均衡

    //缩减分区用coalease+shuffle
    sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6), 6).coalesce(2)
      .mapPartitions(x => x.filter(_ > 0)).glom().map(x => x.sum).collect.foreach(x => println(x))

image-20210123232127718

1.11 repartition

该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的
RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition
操作都可以完成,因为无论如何都会经 shuffle 过程。

	//扩大分区用reparation默认是shuffle
    sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6), 2).repartition(4)
      .mapPartitions(x => x.filter(_ > 0)).glom().map(x => x.sum).collect.foreach(x => println(x))

1.12 sortBy

​ 该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一致。中间存在 shuffle 的过程。
​ 可以有第二个参数,Boolean值,倒序

    val functionToString = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6)).map(x => x).sortBy(x => x).collect().mkString(",")
    val functionToString2 = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6)).map(x => x).sortBy(x => x, false).collect().mkString(",")
    println(functionToString)
    println(functionToString2)

image-20210123232203028

1.13 intersection

对源 RDD 和参数 RDD 求交集后返回一个新的 RDD,要求类型相同

    sc.makeRDD(List(1, 2, 3, 4)).intersection(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))

image-20210123232211525

1.14 union

对原 RDD 和参数 RDD 求并集后返回一个新的 RDD,要求类型相同

    sc.makeRDD(List(1, 2, 3, 4)).union(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))

image-20210123232219885

1.15 subtract

以前一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求差集。要求类型相同

    sc.makeRDD(List(1, 2, 3, 4)).subtract(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))

image-20210123232228466

1.16 zip

将两个 RDD 中的元素,以键值对(元组)的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素。要求拉链分区数量相同,分区数据长度相同

    sc.makeRDD(List(1, 2, 3, 4)).zip(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))

image-20210123232237926

1.17 partitionBy

将数据按照指定 Partitioner 重新进行分区。隐式转换为PairRDDFunctions,二次编译。
区别于coalease和repartition是数量上的改变,二partitionBy是功能上的改变,比如说默认的分区器是 HashPartitioner,或按照key的奇偶分区等规则
1、如果重分区的分区器和当前RDD 的分区器一样怎么办? 源码方法里类型相同,数量相等,则会认为是同一个分区器,返回自己不会产生新的RDD
2、Spark 还有其他分区器吗?三个,Hash和Range(排序时用)和Python(private)
3、如果想按照自己的方法进行数据分区怎么办?自定义

    println(sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3).partitionBy(new HashPartitioner(2)).getNumPartitions)
    println(sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3).getNumPartitions)

image-20210123232246231

1.18 reduceByKey

可以将数据按照相同的 Key 对 Value 进行聚合,和scala语言中一样,都是两两聚合。如果Key只有一个,只返回不计算。还可以指定分区task数

    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("a", 4), ("b", 5), ("c", 6))).reduceByKey(_ + _).foreach(println(_))
    println("-------分割线-----------")
    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("a", 4), ("b", 5), ("c", 6))).reduceByKey(_ + _, 2).foreach(println(_))

image-20210123232254207

1.19 groupByKey

将数据源的数据根据 key 对 value 进行分组,形成一个对偶元组,元组的第二个元素是value,groupBy分组后的元组第二个元素是KV

    println(sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)), 3).groupByKey().getNumPartitions)
    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)), 3).groupByKey(2).foreach(x => x._2.foreach(y => println((x._1, y))))
    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)), 3).groupByKey(new HashPartitioner(2)).foreach(x => x._2.foreach(y => println((x._1, y))))

image-20210123232308261

思考:reduceByKey 和 groupByKey 的区别?

​ 从 shuffle 的角度:reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高。

从功能的角度:reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那么还是只能使用 groupByKey

1.20 aggregateByKey

​ reduceByKey分区内和分区之间的计算规则是相同的,而aggregateByKey是将数据根据不同的规则分别进行不同的分区内计算和分区间计算,规则分别设立。aggregateByKey的返回值和初始化值必须相同

​ aggregateByKey 算子是函数柯里化,存在两个参数列表:

  • 第一个参数列表中的参数表示初始值
  • 第二个参数列表中含有两个参数
  • ​ 2.1 第一个参数表示分区内的计算规则
  • ​ 2.2 第二个参数表示分区间的计算规则
    sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("b", 4))).aggregateByKey(0)(
      (x, y) => math.max(x, y),
      (x, y) => x + y
    ).collect.foreach(println(_))
    println("求相同key的平均值")
    val newRDD = sc.makeRDD(List(("a", 88), ("b", 95), ("a", 91), ("b", 93),
      ("a", 95), ("b", 98)), 2).aggregateByKey((0, 0))(
      (x, y) => {
        (x._1 + y, x._2 + 1)
      },
      (x1, x2) => {
        (x1._1 + x2._1, x1._2 + x2._2)
      }
    )
    newRDD.mapValues {
      case (num, count) => num / count
    }.foreach(println(_))

image-20210123232319204

1.21 foldByKey

当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey

    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
      .foldByKey(0)(_ + _).foreach(println(_))

image-20210123232328746

1.22 combineByKey

​ 最通用的对 key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于aggregate(),combineByKey()允许用户返回值的类型与输入不一致。
​ combineByKey : 方法需要三个参数
​ 第一个参数表示:将相同key的第一个数据进行结构的转换,实现操作
​ 第二个参数表示:分区内的计算规则
​ 第三个参数表示:分区间的计算规则

    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)
    //acc: (Int, Int)数据类型要动态识别
    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.mapValues {
      case (num, count) => num / count
    }.foreach(println(_))

image-20210123232340957

思考:reduceByKey、foldByKey、aggregateByKey、combineByKey 的区别?

四者源码如下:
    reduceByKey:
      combineByKeyWithClassTag[V](
             (v: V) => v, // 第一个值不会参与计算
             func, // 分区内计算规则
             func, // 分区间计算规则
             partitioner
             )

    aggregateByKey :
      combineByKeyWithClassTag[U](
            (v: V) => cleanedSeqOp(createZero(), v), // 初始值和第一个key的value值进行的分区内数据操作
            cleanedSeqOp, // 分区内计算规则
            combOp,       // 分区间计算规则
            partitioner
            )

    foldByKey:
      combineByKeyWithClassTag[V](
            (v: V) => cleanedFunc(createZero(), v), // 初始值和第一个key的value值进行的分区内数据操作
            cleanedFunc,  // 分区内计算规则
            cleanedFunc,  // 分区间计算规则
            partitioner
            )

    combineByKey :
      combineByKeyWithClassTag(
            createCombiner,  // 相同key的第一条数据进行的处理函数
            mergeValue,      // 表示分区内数据的处理函数
            mergeCombiners,  // 表示分区间数据的处理函数
            partitioner
            )
以WordCount为例的简单解释
  • reduceByKey: 相同 key 的第一个数据不进行任何计算,分区内和分区间计算规则相同

  • FoldByKey: 相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则相同

  • AggregateByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则可以不相同

  • CombineByKey:当计算时,发现数据结构不满足要求时,可以让第一个数据转换结构。分区内和分区间计算规则不相同。

      rdd.reduceByKey(_+_)
      rdd.aggregateByKey(0)(_+_, _+_)
      rdd.foldByKey(0)(_+_) 
      rdd.combineByKey(v=>v,(x:Int,y)=>x+y,(x:Int,y:Int)=>x+y) 
    

1.23 sortByKey

在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 K 进行排序的RDD

    val dataRDD1 = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    dataRDD1.sortByKey(true).collect.foreach(println(_))
    dataRDD1.sortByKey(false).foreach(println(_))

image-20210123232353030

1.24 join

​ 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素连接为tuple类型的(K,(V,W))的 RDD。

​ Key类型应该是相同的,如果两个数据源中的key没有匹配上,那么数据不会出现在结果中;如果两个数据源中的key有多个相同的,会依次匹配,出现笛卡尔积,对性能有影响

    val rdd3: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))
    val rdd4: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (3, 6)))
    rdd3.join(rdd4).collect().foreach(println)

image-20210123232401060

1.25&1.26 leftOuterJoin&rightOuterJoin

类似于 SQL 语句的左外连接

    val dr1 = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    val dr2 = sc.makeRDD(List(("e", 1), ("b", 2), ("c", 3)))
    println("leftOuterJoin")
    dr1.leftOuterJoin(dr2).collect().foreach(println)
    println("rightOuterJoin")
    dr1.rightOuterJoin(dr2).collect().foreach(println)

image-20210123232408540

1.27 cogroup

cogroup=connect+group,分组连接,有点像根据重复K拉平后的join操作

在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD

    val cgRDD1 = sc.makeRDD(List(("a", 1), ("a", 2), ("c", 3)))
    val cgRDD2 = sc.makeRDD(List(("a", 1), ("c", 2), ("c", 3), ("d", 3)))
    cgRDD1.cogroup(cgRDD2).collect.foreach(println)

image-20210123232417727

2、行动算子

2.1 reduce

聚集RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据

    val r1 = sc.makeRDD(List(1, 2, 3)).reduce(_ + _)
    println(r1)

image-20210123232426580

2.2 collect

在驱动程序中,以数组Array 的形式返回数据集的所有元素

    val ints11: Array[Int] = sc.makeRDD(List(4,2,3,1)).collect()
    println(ints11.mkString(","))

image-20210123232433046

2.3 count

返回RDD 中元素的个数

    val cnt =  sc.makeRDD(List(4,2,3,1)).count()
    println(cnt)

image-20210123232441679

2.4 first

返回RDD 中的第一个元素

    val first = sc.makeRDD(List(4,2,3,1)).first()
    println(first)

image-20210123232448223

2.5 take

返回一个由RDD 的前 n 个元素组成的数组

    val ints: Array[Int] = sc.makeRDD(List(4,2,3,1)).take(3)
    println(ints.mkString(","))

image-20210123232456225

2.6 takeOrdered

返回该 RDD 排序后的前 n 个元素组成的数组

    val rddt = sc.makeRDD(List(4,2,3,1))
    val ints1: Array[Int] = rddt.takeOrdered(3)
    println(ints1.mkString(","))

image-20210123232504640

2.7 aggregate

​ 分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合

  • aggregate:初始值在参与分区内计算后,再参与分区间的计算
  • aggregateByKey:初始值仅参与分区内计算,不参与分区间的计算
    val i = sc.makeRDD(List(1, 2, 3, 4), 2).aggregate(10)(_ + _, _ + _)
    println(i)

image-20210123232517100

2.8 fold

折叠操作,aggregate 的简化版操作

    println(sc.makeRDD(List(1, 2, 3, 4), 2).fold(10)(_ + _))

image-20210123232526146

2.9 countByKey

统计每种 key 的个数

    sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).countByKey().foreach(println)

image-20210123232534591

2.10 countByValue

用于没有key的单值情况,如果有key则把KV看作组合的单值

    sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).countByValue().foreach(println)

image-20210123232542371

思考:WordCount有多少种RDD实现方法

  1. groupBy+mapValues
  2. map+groupByKey+mapValues
  3. map+reduceByKey
  4. map+aggregateByKey
  5. map+foldByKey
  6. map+combineByKey
  7. map+countByKey
  8. countByValue
  9. map+reduce
  10. map+fold
  11. map+aggregate
    val rdd = sc.makeRDD(List("hello scala", "hello spark"))
    val words = rdd.flatMap(_.split(" "))
    println("第1种")
    words.groupBy(word => word).mapValues(_.size).foreach(println)
    println("第2种")
    words.map((_, 1)).groupByKey().mapValues(_.size).foreach(println)
    println("第3种")
    words.map((_, 1)).reduceByKey(_ + _).foreach(println)
    println("第4种")
    words.map((_, 1)).aggregateByKey(0)(_ + _, _ + _).foreach(println)
    println("第5种")
    words.map((_, 1)).foldByKey(0)(_ + _).foreach(println)
    println("第6种")
    words.map((_, 1)).combineByKey(v => v, (x: Int, y) => x + y, (x: Int, y: Int) => x + y).foreach(println)
    println("第7种")
    words.map((_, 1)).countByKey().foreach(println)
    println("第8种")
    words.countByValue().foreach(println)
    println("第9种")
    import scala.collection.mutable
    words.map(
      word => {
        mutable.Map[String, Long]((word,1))
      }
    ).reduce(
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
          }
        }
        x
      }
    ).foreach(println)
    println("第10种")
    words.map(
      x=>{
        mutable.Map[String,Long]((x,1))
      }
    ).fold(mutable.Map[String,Long]())(
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
        }
        }
        x
      }
    ).foreach(println)
    println("第11种")
    words.map(
      x=>{
        mutable.Map[String,Long]((x,1))
      }
    ).aggregate(mutable.Map[String,Long]())(
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
          }
        }
        x
      },
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
          }
        }
        x
      }
    ).foreach(println(_))

2.11 saveAsTextFile、saveAsObjectFile、saveAsSequenceFile

将数据保存到不同格式的文件中。文件目录不能已存在。saveAsSequenceFile要求必须是KV键值类型

    sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).saveAsTextFile("files/output/04saveAsTextFile")
    sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).saveAsObjectFile("files/output/05saveAsObjectFile")
    sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).saveAsSequenceFile("files/output/06saveAsSequenceFile")

2.14 foreach

分布式遍历RDD 中的每一个元素,调用指定函数

collect以分区为单位采集回到driver端再通过foreach打印,foreach是在executor端分布式采集打印

算子 : Operator(操作)
RDD的方法和Scala集合对象的方法不一样
集合对象的方法都是在同一个节点的内存中完成的。
RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行
为了区分不同的处理效果,所以将RDD的方法称之为算子。
RDD的方法外部的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行。

    println("collect.foreach")
    sc.makeRDD(List(1,2,3,4),2).collect.foreach(println)
    println("直接foreach")
    sc.makeRDD(List(1,2,3,4),2).foreach(println)

ObjectFile(“files/output/05saveAsObjectFile”)
sc.makeRDD(List((“a”, 1), (“a”, 5), (“b”, 2), (“b”, 2))).saveAsSequenceFile(“files/output/06saveAsSequenceFile”)

### 2.14 foreach

分布式遍历RDD 中的每一个元素,调用指定函数

collect以分区为单位采集回到driver端再通过foreach打印,foreach是在executor端分布式采集打印

算子 : Operator(操作)
         RDD的方法和Scala集合对象的方法不一样
         集合对象的方法都是在同一个节点的内存中完成的。
         RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行
         为了区分不同的处理效果,所以将RDD的方法称之为算子。
        RDD的方法外部的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行。

```scala
    println("collect.foreach")
    sc.makeRDD(List(1,2,3,4),2).collect.foreach(println)
    println("直接foreach")
    sc.makeRDD(List(1,2,3,4),2).foreach(println)

image-20210123232552741

3、Spark rdd中求行差值的几种方法整理

数据字段名称:省份,ID,日期,确诊人数(rdd.map(x => (x(12), (x(0).toInt, x(7), x(2).toInt))))
问题:统计每个省份,每天的确诊人数增量。
分析:求第二天减去第一天的确诊人数,即为确诊人数增量

3.1、删首位元素后,zipAll,然后求左右差值即可

    val incrNum = rdd.map(x => (x(12), (x(0).toInt, x(7), x(2).toInt))).groupByKey(1)
      .flatMapValues(x => x.toArray.sortWith(_._2 > _._2)
      .zipAll(x.toArray.sortWith(_._2 > _._2).map(y => y._3).drop(1), (0, "", 0), 0))
      .map(x => (x._2._1._1, x._1, x._2._1._2, x._2._1._3, x._2._1._3 - x._2._2)).sortBy(_._1)

3.2、使用sliding函数,滑动2步生成长度为2的数组,求两个元素的差值,再视情况在头部添加初始元素即可

rdd.map(x => (x(12), (x(0).toInt, x(7), x(2).toInt))).groupByKey()
        .flatMapValues(x=>{
          var arr = x.toArray.sortBy(_._1)
          var arr2 = arr(0)._3+:arr.sliding(2).map(y=>y(1)._3-y(0)._3).toArray
          arr.zip(arr2)
        })

3.3、使用join+getOrElse方法求差值即可

    val value = rdd.map(x => (x(12),( x(7),x(0).toInt, x(2).toInt)))
      .groupByKey().flatMapValues(x=>x.zipWithIndex).map(x=>((x._1,x._2._2),(x._2._1._1,x._2._1._2,x._2._1._3))).cache()

    val value2 = value.map(x=>((x._1._1,x._1._2+1),(x._2._1,x._2._2,x._2._3)))

    val value3=value.leftOuterJoin(value2)
      .map(x=>(x._1._1,x._2._1._1,x._2._1._2,x._2._1._3,x._2._1._3-x._2._2.map(y=>y._3).getOrElse(0)))
      .sortBy(x=>(x._1,x._3))
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值