Spark算子

一:RDD转换算子

RDD根据数据处理方式的不同将算子整体上分为Value类型、双Value类型和Key-Value类型

1、map  (def map[U: ClassTag](f: T => U): RDD[U])

TODO 算子 - 转换
所谓的转换算子,其实就是通过调用RDD对象的方法,将旧的RDD转换为新的RDD通过转换,将多个功能组合在一起.

将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。

object Spark01_RDD_OperTransform {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    // TODO 算子 - 转换
    //    所谓的转换算子,其实就是通过调用RDD对象的方法,将旧的RDD转换为新的RDD
    //    通过转换,将多个功能组合在一起.
    // TODO map -> 转换,映射 (Key => Value)
    // rdd的map算子可以将数据集中的每一条数据进行转换后返回
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    val newRDD: RDD[Int] = rdd.map(_ * 2)
//    newRDD.collect().foreach(println)


    // map算子需要传递一个参数,参数类型为 : Int => U(不确定)
    /*def mapFunction(num : Int) : Int = {
      num * 2
    }
    val newRDD: RDD[Int] = rdd.map(mapFunction)*/
    //至简原则
    /*val newRDD: RDD[Int] = rdd.map(
      num => num * 2
    )*/



    newRDD.collect().foreach(println)
    sc.stop()


  }
}

map案例

map VS flatmap()

flatmap()将整体拆分成个体,个体需要保留

map()将一个长的字符串拆分成短的,只保留需要的部分

object Spark01_RDD_OperTransform_map_demo {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    val rdd: RDD[String] = sc.textFile("datas/apache.log")
    val newRDD: RDD[String] = rdd.map(line => {
      val datas: Array[String] = line.split(" ")
      datas(6)
    })

    newRDD.collect().foreach(println)
    sc.stop()


  }
}

 

 

 2、mappartitions()入参和出参都是迭代器

函数签名 

将待处理的数据以分区为单位发送到计算节点进行处理

def mapPartitions[U: ClassTag](

    f: Iterator[T] => Iterator[U],

    preservesPartitioning: Boolean = false): RDD[U]

map  VS  mappartitions() 类比

Map算子是分区内一个数据一个数据的执行,类似于串行操作。而mapPartitions算子是以分区为单位进行批处理操作。

Map算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。MapPartitions算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据

public class Test2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");

        // A
        for ( String s : list ) {
            map(s);
        }
        System.out.println("***********************");
        // B
        tests(list);
    }
    public static void map( String s ) {
        System.out.println("获取数据库连接");
        System.out.println(s);
        System.out.println("关闭数据库连接");
    }
    public static void tests( List<String> list ) {
        System.out.println("获取数据库连接");
        for ( String s : list ) {
            // 49
            System.out.println(s); // 50
        }
        System.out.println("关闭数据库连接");
    }
}

 案例一:求每个分区的最大值,注意结果需要包装称为迭代器

map()不行因为map只能拿到数据,并不知道数据来自哪个分区

object Spark01_RDD_OperTransform_map {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val newRDD: RDD[Int] = rdd.mapPartitions(
      // mapPartitions算子需要传递一个参数,类型为函数类型: Iterator => Iterator
      list => {
        List(list.max).iterator
      }
    )
    newRDD.collect().foreach(println)
    sc.stop()
  }
}

 3、mapPartitionsWithIndex()

案例:只保留一个分区

map()不行因为map只能拿到数据,并不知道数据来自哪个分区

将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。

 def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)

    // mapPartitionsWithIndex算子需要传递参数,参数的一个值为分区索引,从0开始
    val newRDD: RDD[Int] = rdd.mapPartitionsWithIndex(
      //入参是一个(int,Iterator)  返回一个Iterator
      (index, list) => {
        if (index == 1) {
          list
        } else {
          Nil.iterator
        }
      }
    )
    newRDD.collect().foreach(println)
    sc.stop()
  }
}

 4、groupBy()

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

一个组的数据在一个分区中,但是并不是说一个分区中只有一个组

def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]

groupBy算子需要传递一个参数,参数类型为函数类型:Int => K
groupBy算子返回的结果类型为元组,第一个元素就是分组的标记,第二个元素就是相同标记的数据集合
groupBy算子的第二个参数可以改变分区数量

object Spark01_RDD_OperTransform_map {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)

    //groupBy()需要传递一个参数,参数类型为函数类型 : Int => K
    //RDD[(String, Iterable[Int])]表示相同数据的集合放到一块
    /*val groupRdd: RDD[(String, Iterable[Int])] = rdd.groupBy(
      num => {
        if (num % 2 == 0) {
          "偶数"
        } else {
          "奇数"
        }
      }
    )*/
    /*val groupRdd: RDD[(Int, Iterable[Int])] = rdd.groupBy(
      num => {
        if (num % 2 == 0) {
          0
        } else {
          1
        }
      }
    )*/
    /*val groupRdd: RDD[(Boolean, Iterable[Int])] = rdd.groupBy(
      num => {
        num % 2 == 0
      }
    )*/
    //num只用了一次  可以只看结果
    val groupRdd: RDD[(Boolean, Iterable[Int])] = rdd.groupBy(_ % 2 == 0)
    groupRdd.collect().foreach(println)
    sc.stop()
  }
}

 shuffle会将完整的计算流程一分为二,变成两个阶段,一个写的阶段,一个读的阶段   如果数据只有一条数据 只要分组打乱分区,则一定会落盘

所有含有shuffle的算子,都有改变分区的能力因此groupBy()第二个参数可以传分区的个数。一个RDD计算的过程很大程度上取决于shuffle(),shuffle()快就快,shuffle()慢就慢

reduceByKey()也有shuffle()和改变分区的能力

案例:将List("Hello", "hive", "hbase", "Hadoop")根据单词首写字母进行分组。

在Scala中可以通过 List.apply(1, 2, 3) 创建一个List对象, apply方法定义在List类的伴生对象中, 像之前所说的, 我们可以简化apply方法, 直接通过 List(1, 2, 3) 创建一个List实例.

默认创建对象都调用了apply()

object Spark01_RDD_OperTransform_map {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    //val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val rdd: RDD[String] = sc.makeRDD(List("Hello", "hive", "hbase", "Hadoop"))
    /*
    方式一
    val groupRdd: RDD[(String, Iterable[String])] = rdd.groupBy(
      //      传递一个参数,参数类型为函数类型:String => K
      //返回的结果类型为元组,第一个元素就是分组的标记,第二个元素就是相同标记的数据集合
      word => {
        word.substring(0, 1)
      }
    )*/
    val groupRdd: RDD[(Char, Iterable[String])] = rdd.groupBy(
      word => {
      //scala中没有字符串
      //apply()可以省略  隐式转换
//      word.apply(0)
        word(0)
      }
    )
    groupRdd.collect().foreach(println)
    sc.stop()
  }
}

案例:从服务器日志数据apache.log中获取每个时间段访问量(类似于wordcount)

案例三:wordcount

groupBy算子可以实现 WordCount 功能 ( 1 / 10 ) 但是不能独立实现,因为没有聚合的功能

object Spark01_RDD_OperTransform_map {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    //textFile是一行一行读  因此 是String
    val groupRdd: RDD[String] = sc.textFile("datas/apache.log")
    val newGoup: RDD[(String, Int)] = groupRdd.map(
      line => {
        val datas: Array[String] = line.split(" ")
        val time: String = datas(3)
        val times: Array[String] = time.split(":")
        val hour: String = times(1)
        (hour, 1)
      }
      //((16,1),CompactBuffer(143.233.204.28 - - 17/05/2015:16:05:20 +0000 GET /blog/geekery/ssl-latency.html, 143.233.204.28 - - 17/05/2015:16:05:54 +0000 GET /blog/geekery/ssl-latency.html
      //对于每一个传入的元素获取时间段
    )
    //    返回的结果类型为元组,第一个元素就是分组的标记,第二个元素就是相同标记的数据集合
    //类型的转换,也可以是值的转换。
    val hourGroup: RDD[(String, Iterable[(String, Int)])] = newGoup.groupBy(_._1)
    //mapValues心生成的集合  key不变  values 求和
    val result: RDD[(String, Int)] = hourGroup.mapValues(_.size)
    result.collect().foreach(println)
    sc.stop()
  }
}

5、flatMap

def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]

将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射

TODO 将整体数据拆分成个体数据的操作,称之为扁平化。spark中只有flatmap()没有flatten()

案例:将List(List(1,2)List(3,4))进行扁平化操作

object Spark01_RDD_OperTransform_wordcount {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[List[Int]] = sc.makeRDD(
      List(
        List(1, 2),
        List(3, 4))
    )
    rdd.flatMap(list => list).collect().foreach(println)
  }
}
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    /*val rdd: RDD[List[Int]] = sc.makeRDD(
      List(
        List(1, 2),
        List(3, 4))
    )*/
    // TODO 将整体数据拆分成个体数据的操作,称之为扁平化。
    // flatMap算子需要传递一个参数,这个参数得类型未函数类型:item => Container
    val rdd: RDD[Int] = sc.makeRDD(List(
      1, 2, 3, 4
    ))

    rdd.flatMap(num => List(num)).collect().foreach(println)
//    rdd.flatMap(list => list).map(_*2)

  }
}

 案例:将List(List(1,2),3,List(4,5))进行扁平化操作

object Spark01_RDD_OperTransform_flatmap {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[Any] = sc.makeRDD(
      List(
        List(1, 2),
        3,
        List(4, 5))
    )
    rdd.flatMap{
          //模式匹配不考虑泛型
          //即如果是list的集合 则直接返回list 然后flatmap()会对其进行扁平化
      case list: List[_] => {
        list
      }
      case num => {
        //即如果是单独的数值 则包装成集合 然后flatmap()会对其进行扁平化
        List(num)
      }

    }.collect().foreach(println)

    sc.stop()

//    newRDD.collect().foreach(println)
  }
}

6、glom()

不管是将整体转成个体还是将个体转成整体,都是为了方便计算。

def glom(): RDD[Array[T]]

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

glom算子可以将分区内的个体数据转换为分区的整体数据

补充:

泛型和super都是只在编译时有效

object Spark01_RDD_OperTransform_glom {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
    val newRDD: RDD[Array[Int]] = rdd.glom()
    //求每个分区的最大值  给分区内排序
    val maxRDD: RDD[Int] = newRDD.map(_.max)

    val d: Double = maxRDD.sum()
    println(d)
//    maxRDD.collect().foreach(println)
//    maxRDD.collect().foreach(println)
    sc.stop()
  }
}

每个分区都是都是存的是Array[Int]类型的RDD对象
val newRDD: RDD[Array[Int]] = rdd.glom()

7、filter

函数签名  def filter(f: T => Boolean): RDD[T]


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

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    val newRDD: RDD[Int] = rdd.filter(
      _ % 2 == 0
    )
    newRDD.collect().foreach(println)
    sc.stop()
  }

案例:从服务器日志数据apache.log中获取2015年5月17日的请求路径

object Spark01_RDD_OperTransform_filter {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    /*val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)

    val newRDD: RDD[Int] = rdd.filter(
      _ % 2 == 0
    )*/
    val fileRDD: RDD[String] = sc.textFile("datas/apache.log")

    fileRDD.filter(
      line => {
        val datas: Array[String] = line.split(" ")
        val time: String = datas(3)
        //给一个前缀  直接获取时间
        //包含17/05/2015  的行返回
        time.startsWith("17/05/2015")
      }
      //重新转换集合
    ).map(
      line => {
        val words: Array[String] = line.split(" ")
        words(6)
      }
    ).collect().foreach(println)
    sc.stop()
  }
}

8、sample()

def sample(

  withReplacement: Boolean,

  fraction: Double,

  seed: Long = Utils.random.nextLong): RDD[T]

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

抽取(采样)数据:从指定的数据集中随机抽取部分数据
sample算子可以传递多个参数:
        第一个参数表示抽取的方式
        抽取放回 : true,  抽取不放回 : false,

        第二个参数表示每一条数据被抽取的概率
        第三个参数表示随机数种子 : 随机数不随机(随机算法)

object Spark01_RDD_OperTransform {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val rdd: RDD[Int] = sc.makeRDD(1 to 10)
    val newRDD: RDD[Int] = rdd.sample(true, 0.5, 1)
    newRDD.collect().foreach(println)
    sc.stop()
  }
}

类似于hbase中的布隆过滤器(Hah碰撞) => hash()

 9、distinct()分布式去重

def distinct()(implicit ord: Ordering[T] = null): RDD[T]

def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

TODO  scala 中的单点  去重    val seen = mutable.HashSet[A]()
TODO distinct算子将数据集中重复数据去除   分布式去重原理

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val newRDD: RDD[Int] = sc.makeRDD(
      List(1, 1, 1), 2
    )
    List(123,12).distinct
    newRDD.distinct(2).collect().foreach(println)
    //TODO  scala 中的单点  去重    val seen = mutable.HashSet[A]()
    // TODO distinct算子将数据集中重复数据去除   分布式去重原理
    //map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)
    //(1, null),(1, null),(1, null)
    //(1, (null, null, null))
    // (1, (null, null))
    // (1, null)
    // 1

    sc.stop()
  }

10、coalesce()  VS  repartition()

def coalesce(numPartitions: Int, shuffle: Boolean = false,

           partitionCoalescer: Option[PartitionCoalescer] = Option.empty)

          (implicit ord: Ordering[T] = null)

  : RDD[T]

数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率

todo 缩减分区  默认情况下不适用shuffle,数据不会被打乱重新组合(只考虑了分区的多和少,不关心数据是否倾斜,只关注分区数量的多少)

可能会导致数据不均衡,出现数据倾斜,为了防止数据倾斜,可采用shuffle,传递第二个参数。

coalesce算子主要应用于缩减分区,默认不使用shuffle
所以如果扩大分区,不使用shuffle是不可以的。
repartition算子主要应用于扩大分区,肯定有shuffle

11、sortBy()

... 错误: 隐式转换未找到
sortBy算子用于将数据集中的每一条数据增加排序的标记(维度),默认为升序
rdd.sortBy(num=>num).collect().foreach(println)

scala中的元组的排序

Tuple元组排序,默认排序第一个,如果第一个相同,默认排序第二个 ...

object Test {
  def main(args: Array[String]): Unit = {
    val list = List(
      (20,3000),
      (30,1000),
      (30,2000),
      (20,2000),
    )
//    list.sortBy(t => t)(Ordering.Tuple2[Int,Int].reverse).foreach(println)
    //Tuple元组排序,默认排序第一个,如果第一个相同,默认排序第二个 ...
    list.sortBy(t =>t)(Ordering.Tuple2[Int,Int](
      Ordering.Int,
      Ordering.Int.reverse
    )).foreach(println)
  }
}

12、双值类型

交集 并集 差集  zip 

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)

    val rdd1: RDD[Int] = sc.makeRDD(
      List(1, 2, 3, 4), 2
    )
    /*val rdd2: RDD[Int] = sc.makeRDD(
      List(3, 4, 5, 6), 2
    )*/
    val rdd2: RDD[Int] = sc.makeRDD(
      //Can only zip RDDs with same number of elements in each partition
      //Can't zip RDDs with unequal numbers of partitions
      List(3, 4, 5, 6,7,8), 3
    )
    val rdd3 = sc.makeRDD(
      List("3","4","5","6"),2
    )
    //交集 [3,4]
    rdd1.intersection(rdd2).collect().foreach(println)
    // 并集 [1,2,3,4,3,4,5,6]
    rdd1.union(rdd2).collect().foreach(println)
    // 差集 [1,2]
    rdd1.subtract(rdd2).collect().foreach(println)
    //拉链[(1,3),(2,4),(3,5),(4,6)]
    rdd1.zip(rdd2).collect().foreach(println)
    //双值类型 必须保证两个集合中的类型要一样
//    rdd1.union(rdd3)
    //zip对类型没有要求
    rdd1.zip(rdd3)

//    newRDD.collect().foreach(println)
    sc.stop()
  }

13、partitionBy()

TODO partitionBy算子表示将数据按照指定的分区规则进行重分区
repartition : 重分区(分区数量)
partitionBy : 重分区(数据的位置), 数据路由(Hash定位),分区器
如果想要让数据重新进行分区,那么需要传递分区规则对象(分区器)
Spark中默认常用的分区器:RangePartitioner & HashPartitioner(默认)

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd1: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("b", 2),
        ("c", 3), ("d", 4)
      ), 2
    )
    // TODO partitionBy算子表示将数据按照指定的分区规则进行重分区
    // repartition : 重分区(分区数量)
    // partitionBy : 重分区(数据的位置), 数据路由(Hash定位),分区器
    //    如果想要让数据重新进行分区,那么需要传递分区规则对象(分区器)
    // Spark中默认常用的分区器:RangePartitioner & HashPartitioner(默认)

    rdd1.partitionBy(new HashPartitioner(2)).saveAsTextFile("output")

    sc.stop()
  }

如果重分区的分区器和当前RDD的分区器一样怎么办?

14、reduceByKey()

可以将数据按照相同的Key对Value进行聚合

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)

    // TODO reduceByKey  算子用于将数据集中相同的key的value数据聚合在一起实现两两聚合
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 2), ("a", 3)
      )
    )
    //spark 的算子有很多是以byKey结尾的,就是说将K-V独立使用
    rdd.reduceByKey(_+_).collect().foreach(println)
    sc.stop()
  }

15、groupByKey()

将数据源的数据根据key对value进行分组

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd1: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 2), ("a", 3)
      ), 2
    )
    // groupByKey算子会将数据按照key对value进行分组
    //list中存储的元素是一个个元组
//    rdd1.groupBy(_._1).collect().foreach(println)
    // TODO groupByKey算子也可以实现 WordCount ( 3 / 10 )
    rdd1.groupByKey().collect().foreach(println)
    // TODO groupByKey算子也可以实现 WordCount ( 3 / 10 )
    rdd1.groupByKey().mapValues(_.sum).collect().foreach(println)
    sc.stop()
  }

groupByKey  VS  reduceByKey

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

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

 16、aggregateByKey()   VS  foldByKey()

将数据根据不同的规则进行分区内计算和分区间计算

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

 def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("b", 2), ("a", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )
    //TODO 数据集中相同Key的分区内的最大值, 分区间求和
    // TODO 如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。
    //分区内求最大值  分区间求和
    //aggregateByKey  存在参数柯里化, 有多个参数列表
    //第一个参数列表中有一个参数
//        计算初始值
    //第二个参数列表中有两个参数
    //第一个参数表示:分区内计算规则
    //第二个参数表示 :分区间计算规则
    rdd.aggregateByKey(0)(
      //需要有初始值  用于比较确定比较大的值  两两迭代进行比较
      (x,y) => math.max(x,y),
      (x,y) => x + y
    )

    rdd.groupByKey().mapValues(_.sum).collect().foreach(println)
    sc.stop()
  }

 初始值不一样

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("b", 2), ("a", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )
    //TODO 数据集中相同Key的分区内的最大值, 分区间求和
    // TODO 如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。
    //分区内求最大值  分区间求和
    //aggregateByKey  存在参数柯里化, 有多个参数列表
    //第一个参数列表中有一个参数
//        计算初始值
    //第二个参数列表中有两个参数
    //第一个参数表示:分区内计算规则
    //第二个参数表示 :分区间计算规则
    rdd.aggregateByKey(5)(
      //需要有初始值  用于比较确定比较大的值  两两迭代进行比较
      (x,y) => math.max(x,y),
      (x,y) => x + y
    ).collect().foreach(println)
    sc.stop()
  }
//aggregateByKey实现wordCount
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("b", 2), ("a", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )
    //TODO 数据集中相同Key的分区内的最大值, 分区间求和
    // TODO 如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。
    //分区内求最大值  分区间求和
    //aggregateByKey  存在参数柯里化, 有多个参数列表
    //第一个参数列表中有一个参数
//        计算初始值
    //第二个参数列表中有两个参数
    //第一个参数表示:分区内计算规则
    //第二个参数表示 :分区间计算规则
    //不需要看key 底层默认识别按照key进行统计
    rdd.aggregateByKey(0)(
      //需要有初始值  用于比较确定比较大的值  两两迭代进行比较
      //相同key的求最大值  分区内的相同的key的相加
      (x,y) => x + y,
      //分区间的 相同的key的相加
      (x,y) => x + y
    ).collect().foreach(println)
    sc.stop()
  }

17、combineByKey

需求:求相同key的平均值   GroupByKey()  ReduceByKey()   

flatmap() map()  mapPartition()等方法用的比较多,因为可以转换数据的格式

 

 def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )
    // TODO 求相同key的平均值
    /*val groupRDD: RDD[(String, Iterable[Int])] = rdd.groupByKey()

    val finalRDD: RDD[(String, Int)] = groupRDD.mapValues(
      list =>
        list.sum / list.size
    )*/
    // ("a", (1,1))), ("a", 2)), ("b", (3,1)),("b", 3)
    //("a", (1,1))), ("a", (2,1))), ("b", (3,1)),("b", (3,1))
    //使变成元组,可以结合元组求出相同key的长度
    val newRDD: RDD[(String, (Int, Int))] = rdd.mapValues((_, 1))
//    newRDD.collect().foreach(println)
    //两两聚合  key是String  V是tuple
    //数据格式的改变    
    newRDD.reduceByKey(
      (t1,t2) => {
        (t1._1 + t2._1 ,t1._2 + t2._2)
      }
    ).mapValues{ //处理求平均值
      case (total , cnt) => {
            total / cnt
      }
    }
      .collect().foreach(println)
//    groupRDD.partitionBy(new HashPartitioner(2)).saveAsTextFile("output")

    sc.stop()
  }
def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )
    // TODO 如果在计算过程中,只需要对相同key的第一条数据进行处理的功能,可以采用特殊的算子
    //  combineByKey算子需要传递三个参数:
    //    第一个参数表示 : 第一个V的处理逻辑
    //    第二个参数表示 : 分区内计算规则
    //    第三个参数表示 : 分区间计算规则
    rdd.combineByKey(
      //第一个V的逻辑
      (v) => (v,1),
      //除了第一个V的逻辑     t:(Int,Int) 为第一个v的类型为tuple
      // 因为第一个参数可以进行转变
      //("a",(3,2)) ("b",3)
      //("b",(9,1)) ("a",6)
      (t:(Int,Int) , v) => {
        (t._1 + v , t._2 + 1)
      },
      //分区内计算完之后  是两个tuple
      //("a",(3,2)) ("b",(3,1))
      //("b",(9,2)) ("a",(6.1))
      (t1:(Int,Int),t2:(Int,Int)) =>{
        (t1._1 + t2._1 , t1._2 + t2._2)
      }
    ).collect().foreach(println)

 wordCount (combineByKey)

  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )
    // TODO 如果在计算过程中,只需要对相同key的第一条数据进行处理的功能,可以采用特殊的算子
    //  combineByKey算子需要传递三个参数:
    //    第一个参数表示 : 第一个V的处理逻辑
    //    第二个参数表示 : 分区内计算规则
    //    第三个参数表示 : 分区间计算规则
    val finalRDD: RDD[(String, Int)] = rdd.combineByKey(
      v => v,
      (num1: (Int), num2) => {
        num1 + num2
      },
      (t1: (Int), t2: (Int)) => {
        t1 + t2
      }
    )
    finalRDD.collect().foreach(println)
    sc.stop()
  }
def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )
    //相同的key进行聚合
    rdd.reduceByKey(_ + _).collect().foreach(println)
    //aggregateByKey()
    rdd.aggregateByKey(0)(
      (_+_),(_+_)
    ).collect().foreach(println)
    //foldByKey
    rdd.foldByKey(0)(_+_).collect().foreach(println)
    //combineByKey
    rdd.combineByKey(v => v,(num :(Int),v) => num + v ,(v1:(Int),v2:(Int)) => v1 + v2).collect().foreach(println)

    sc.stop()
  }

18、sortByKey()

在一个(K,V)的RDD上调用,K必须实现Ordered接口(特质),返回一个按照key进行排序的  只是对key进行排序 和v没有关系

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    /*val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2
    )*/
    val rdd: RDD[(User, Int)] = sc.makeRDD(
      List(
        (new User(), 1), (new User(), 6), (new User(), 3),
        (new User(), 4), (new User(), 5), (new User(), 2)
      ), 2
    )
    //sortByKey算子是根据key进行排序  //没有指定排序规则 sortByKey 会报红
    rdd.sortByKey().collect().foreach(println)
    sc.stop()
  }
  class User extends Ordered[User]{
    var age = 20 
    override def compare(that: User): Int = {
      -1
    }
  }

18、双Value 算子  join()

Join 为双Value类型操作
join算子体现的是数据的关系
join算子将两个数据集中相同的key的数据,连接在一起  不同key之间没有关系join算子可能存在笛卡尔乘积    join算子可能存在shuffle操作    如果能够使用其他算子实现的功能,那么不推荐使用join

leftOuterJoin    rightOuterJoin     fullOuterJoin

cogroup() 多数据集进行相连

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
    val sc = new SparkContext(spakConf)
    val rdd1: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("b", 2), ("c", 3)
      )
    )
    val rdd2: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("d", 5), ("c", 6),("a", 4)
      )
    )
    //join算子体现的是数据的关系
//    join算子将两个数据集中相同的key的数据,连接在一起
    rdd1.join(rdd2).collect().foreach(println)
    rdd1.leftOuterJoin(rdd2).collect().foreach(println)
    rdd1.rightOuterJoin(rdd2).collect().foreach(println)
    rdd1.fullOuterJoin(rdd2).collect().foreach(println)

    sc.stop()
  }

案例:

agent.log:时间戳,省份,城市,用户,广告,中间字段使用空格分隔。

先分组后统计 优化之后先统计在分组  工作中 一定要先统计在分组  先聚合在分组

性能会大大提升

行动算子

所谓的行动(Action)算子,其实就是RDD用于触发作业执行的方法,类似于IO中的read方法,转换算子的返回值为RDD,行动算子的返回值为具体的结果

行动算子和作业的关系 :1 对 1

1、reduce()

没有key的概念      行动算子的返回值为具体的结果

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
    // TODO reduce算子可以触发作业的执行,分区内先两两计算,分区间再两两计算
    //  分区内计算都是在Executor。
    //  分区间计算都是在Driver
    val result1: Int = rdd.reduce(
      (x, y) => {
        println(Thread.currentThread().getName+ x + " + " + y)
        x + y
      }
    )
    println(result1)
    sc.stop()
  }

2、

count()  返回RDD中元素的个数

first()   返回RDD中的第一个元素

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

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

3、aggregate()

aggregateByKey算子的初始值只能参与分区内的计算,而aggregate算子的初始值还可以参与分区间的计算

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
    //结果10
    val result: Int = rdd.aggregate(0)(
      _ + _,
      _ + _
    )
    // aggregateByKey算子的初始值只能参与分区内的计算,而aggregate算子的初始值还可以参与分区间的计算
//结果40
    val result1: Int = rdd.aggregate(10)(_ + _, _ + _)
    println(result1)
    sc.stop()
  }

 4、countByValue   VS  countByKey

countByValue算子可以实现 WordCount

countByKey算子可以实现 WordCount

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    /*val rdd: RDD[(String,Int)] = sc.makeRDD(
      List(
        ("a", 1), ("a", 1), ("a",1 )
      ), 2
    )
    val stringToLong: collection.Map[String, Long] = rdd.countByKey()
    println(stringToLong)*/
    //单Value(单一数据集)数据操作,双Value(多个数据集)数据操作
    val rdd: RDD[(String, Int)] = sc.makeRDD(
      List(
        ("a", 1), ("b", 1), ("c", 1)
      ), 2
    )
    println(rdd.countByValue())
    sc.stop()
  }

5、save

rdd.saveAsTextFile("output1")
rdd.saveAsObjectFile("output2")
rdd.saveAsSequenceFile("output3")

 

6、foreach()

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    // 单Value(单一数据集)数据操作,双Value(多个数据集)数据操作
    val rdd: RDD[Int] = sc.makeRDD(
      List(
        //tuple也属于对象
        1,2,3,4
      ),2
    )
    // collect方法用于按照分区顺序采集数据
    // foreach方法
    rdd.collect().foreach(println)
    println("********************************")
    rdd.foreach(println)
    sc.stop()
  }

 7、序列化

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    // 单Value(单一数据集)数据操作,双Value(多个数据集)数据操作
    val rdd: RDD[Int] = sc.makeRDD(
      List(
        //tuple也属于对象
        1,2,3,4
      ),2
    )
    //new User() 是在主线程中被创建的  而主线程的main方法是在Driver端被调用的
    //user实在Driver端
    val user: User = new User()
    // foreach算子表示对数据集进行分布式循环遍历,遍历的逻辑是在不同的节点(Executor)上执行
    // 算子内部的执行逻辑在Executor端执行的
    // 如果Executor端使用到了Driver端的数据,那么必须保证使用的数据能够序列化
    rdd.foreach(
      num => {
        println(user.age + num)
      }
    )
    sc.stop()
  }
  // scala语言就是一个代码生成器
  // 样例类自动实现可序列化接口
  case class  User(){
    val age = 30
  }

 

9、闭包检测

Java中的异常打印到控制台什么意思?为了程序的健壮性

 

 

 

 

object Spark01_RDD_Serial {
  def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    val rdd: RDD[Int] = sc.makeRDD(List[Int]())
    // Driver
    val user = new User()
    // TODO 闭包
    // Spark在运行作业之前,会判断程序逻辑中是否包含闭包
    // 如果存在闭包操作,那么需要检测闭包中使用的数据是否能够序列化,如果不能,那么发生错误
    // 这个操作称之为闭包检测
    rdd.foreach(
      num => {
        //Executor
        println(user.age + num)
      }
    )

    sc.stop()
  }
  // scala语言就是一个代码生成器
  // 样例类自动实现可序列化接口
  class User(){
    val age = 30
  }
}

 

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    val rdd: RDD[String] = sc.makeRDD(
      List(
        "Hello", "Spark", "Hive", "Scala"
      )
    )
    rdd.filter(_.startsWith("H")).collect().foreach(println)
    new Search("H").matchData(rdd)

    sc.stop()
  }
  class Search(q :String) extends Serializable {
    def matchData(rdd : RDD[String]) : Unit = {
      rdd.filter(_.startsWith(this.q)).collect().foreach(println)
    }
  }

 class VS object

成员变量一般声明在class中,静态一般声明在object中

shuffle()

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值