Spark之RDD的Transformation算子map,flatMap,fliter,mapPartition,groupBy/reduceByKey,foldByKey,distinct.(4)

一   Transformation算子

Transformation : 即转换算子 ,调用转换算子会生成一个新的RDD ,Transformation 是 Lazy 的,不会触发 job 执行 .

基本上分为两类 : 

1) 不产生shuffle的算子(窄依赖)

2) 产生shuffle的算子(宽依赖)

二   对RDD的操作(创建,查看)

1  创建 RDD 的方法

1.1  通过并行化方法 ,将 Driver 端的集合转成 RDD

将Driver端的scala集合并行化成RDD,RDD中并没有真正要计算的数据,只是记录以后从集合中的哪些位置获取数据

val  rdd1:RDD[Int] = sc.parallelize(Array(1,2,3,4,5,6,7),2)     ---这里的分区数指定是 2

1.2  从 HDFS 指定的目录创建RDD

指定以后从哪里读取创建RDD,可以是多种文件系统,需要指定文件系统的协议,如hdfs://,flile://,s3://等

val  lines:RDD[String] = sc.textFile("hdfs://linux04:8020/log" ,2)       --- 2 可以省略不写 ,默认就是 2

log :  是linux04节点上 hdfs 中跟目录下的一个文件夹

8020 : 是linux04 节点hdfs 的端口号

注意 : 使用上面两种方式创建 RDD 都可以指定分区的数量.

1.3  使用 makeRDD 方法创建RDD

将Driver端的scala集合并行化成RDD,RDD中并没有真正要计算的数据,只是记录以后从集合中的哪些位置获取数据

    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)

    val arr = List(1,2,3,4)
    val listRDD: RDD[Int] = sc.makeRDD(arr, 2)
    --index 表示分区的索引 ;it 表示对应分区的迭代器,一个分区的迭代器抓取该分区的一条条数据
    val result: Array[String] = listRDD.mapPartitionsWithIndex((index, it) => {
      it.map(li => s"分区号为 :$index ,对应的迭代器为 :$li")
    }).collect()
    result.foreach(println)
    
     结果为 : 分区号为 :0 ,对应的迭代器为 :1   分区号为 :0 ,对应的迭代器为 :2
             分区号为 :1 ,对应的迭代器为 :3    分区号为 :1 ,对应的迭代器为 :4
     

2   查看RDD 的数量

val  rdd1:RDD[Int] = sc.parallelize(Array(1,2,3,4,5,6,7),2)

rdd1.partitions.length

三   RDD 常用的 Transformation 算子

1  不产生shuffle的算子(窄依赖)

1.1  map 算子 ,功能是做映射

val  rdd1:RDD[Int] = sc.parallelize(List(3,4,5,6,2,4,1,10)).map(_*2)         

 List集合中的每个元素都*2 ,然后返回一个新的RDD

1.2  flatMap 算子 ,先 map 再压平展开 ,spark 中没有 flatten 方法

1)  val  rdd2 = sc.parallelize(Array("a  b  c"," d  e  f", "h  i  j"))

  val strings: Array[String] = rdd2.flatMap(_.split("\\s+")).collect()

      println(strings.toBuffer)           结果为 : ArrayBuffer(a, b, c, d, e, f, h, i, j)

2)   val  rdd3 = sc.parallelize(List(List("a  b  c","d  e  g"),List("c  f   g"," i   h  j")))

      val result: Array[String] = rdd3.flatMap(_.flatMap(_.split("\\s+"))).collect()

      println(result.toBuffer)             结果为 : ArrayBuffer(A, B, C, D, E, F, a, b, c, d, e)

1.3  filter算子 ,功能为过滤数据

val rdd4: RDD[Int] = sc.parallelize(List(3, 4, 6, 5, 2, 7, 8, 9, 10))
val result: Array[Int] = rdd4.filter(_ % 2 == 0).collect()
println(result.toBuffer)    ---ArrayBuffer(4, 6, 2, 8, 10)

1.4  mapPartition ,将数据以分区的形式返回进行map操作 ,一个分区对应一个迭代器 ,该方法和map方法类似 ,只不过该方法的参数由RDD中的每一个元素变成了RDD中的每一个分区的迭代器 ,如果在映射的过程中需要频繁创建额外的对象 ,使用 mapPartition要比map高效的多 .(map针对的是每一个元素 ,mapPartition针对的是每一个分区的迭代器)

object mapPartition {
  def main(args: Array[String]): Unit = {
    ---获取conf
    val conf = new SparkConf().setAppName("mapPartition").setMaster("local[*]")
    ---创建 sparkContext
    val sc = new SparkContext(conf)
    ---使用 sc ,创建原始的RDD
    val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5), 2)
    val partitions: RDD[Int] = rdd1.mapPartitions(it => it.map(x => x * 10))
    val result: Array[Int] = partitions.collect()
    println(result.toBuffer)     ---ArrayBuffer(10, 20, 30, 40, 50)
  }
}
object mapPartition {
  def main(args: Array[String]): Unit = {
    ---获取conf
    val conf = new SparkConf().setAppName("mapPartition").setMaster("local[*]")
    ---创建 sparkContext
    val sc = new SparkContext(conf)
    ---使用sc ,创建一个原始的RDD ,不使用默认的2个分区 ,而是手动设置3个分区
    val lines: RDD[String] = sc.textFile(args(0),3)
    ---将读取的元素遍历然后转大写
    val upper: RDD[String] = lines.map(_.toUpperCase)
    ---将转大写的元素保存到指定  C:\Users\123\Desktop\spark\out1  目录下
    upper.saveAsTextFile(args(1))    
    ---释放资源
    sc.stop()
  }
}


C:\Users\123\Desktop\spark\out1 目录下的结果为 : 
part-00000   ERROR  SPARK  FLINK   / HIVE  HBASE  SPRING
part-00001   ERROR  SPARK  FLINK HBASE  SPRING
part-00002   HBASE  SPRING
可以看到 ,处理的结果被分在三个区中保存

1.4  mapPartitionsWithIndex ,类似于 mapPartitions 以分区为单位进行操作, 一个分区就是一个迭代器 ,并同时获取分区的编号 ;不过函数要输入两个参数 ,第一个参数为分区的索引, 第二个参数是对应分区的迭代器 .函数的返回的是一个经过函数转换的迭代器 .

object MapPartitionsWithIndex01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val listRDD: RDD[Int] = sc.parallelize(List(1,2,3,4))
    val result: Array[String] = listRDD.mapPartitionsWithIndex((index, it) => {
      --index 表示分区的索引 ;it 表示对应分区的迭代器,一个分区的迭代器抓取该分区的一条条数据
      it.map(li => s"partition :$index , val :$li")
    }).collect()
    println(result.toBuffer)
     --结果为 :ArrayBuffer(partition :0 , val :1, partition :0 , val :2,
     --                    partition :1 , val :3, partition :1 , val :4)
  }
}

1.5  union (将两个RDD合并成一个RDD,生成的RDD分区的数量是原来两个RDD分区数量之和)

object Union01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    --创建两个RDD
    val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3), 2)
    val rdd2: RDD[Int] = sc.parallelize(List(4, 5, 6), 3)
    --将两个RDD合并成一个RDD ,并且将两个RDD的分区数量相加
    val rdd3: RDD[Int] = rdd1.union(rdd2)
    println(rdd3.collect().toBuffer)  --- ArrayBuffer(1, 2, 3, 4, 5, 6)
    println(rdd3.partitions.length)   --- 5
  }
}

1.6  keys 和 values 和 mapValues

keys : Rdd中的数据类型为对偶元组,可以获取全部对偶元组中的第一个

values : Rdd中的数据类型为对偶元组,可以获取全部对偶元组中的第二个

mapValues : RDD中的数据类型为对偶元组 ,可以将全部对偶元组中的第二个取出来,然后对其进行处理

object KeysAndValues {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val tupleRDD: RDD[(String, Int)] = sc.parallelize(List(("a" -> 1), ("b" -> 2), ("c", 3)))
    --keys方法 :将对偶元组的第一个元素都取出来
    val keysRes: RDD[String] = tupleRDD.keys
    println(keysRes.collect().toBuffer)       -- ArrayBuffer(a, b, c)

    --values方法 :将对偶元组的第二个元素都取出来
    val valuesRes: RDD[Int] = tupleRDD.values
    println(valuesRes.collect().toBuffer)     -- ArrayBuffer(1, 2, 3)

    --mapValues方法:将对偶元组的第二个元素取出来 ,可以对其进行处理,如下的 将每个元素乘以10
    val mapValuesRes: RDD[(String, Int)] = tupleRDD.mapValues(f => f * 10)
    println(mapValuesRes.collect().toBuffer)    -- ArrayBuffer((a,10), (b,20), (c,30))
    --不使用 mapValues 算子也可以实现该算子相同的功能
    val result: RDD[(String, Int)] = tupleRDD.map(it => (it._1, it._2 * 10))
    println(result.collect().toBuffer)         --ArrayBuffer((a,10), (b,20), (c,30))
  }
}

1.7  flatMapValues : 只对对偶元组的value值进行操作 ,然后将value值展平

object FlatMapValues01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val list1 = List(("A","1,2,3"),("B","3,4"),("C","6,7"))
    val tupleRDD: RDD[(String, String)] = sc.parallelize(list1)
    --flatMapValues :只对对偶元组的value值进行切割操作 ,然后将value值展平
    val result: RDD[(String, String)] = tupleRDD.flatMapValues(it => it.split(","))
    println(result.collect().toBuffer)
    结果为 :ArrayBuffer((A,1), (A,2), (A,3),
                         (B,3), (B,4),
                         (C,6), (C,7))
     
    --不使用flatMapValues算子 ,也实现该算子的功能
    val result1: Array[(String, String)] = tupleRDD.flatMap(t => {
      val key = t._1      --这里的key没有使用到 ,只是拿出来又装进去,所以这个方法不是很好
      val numbers = t._2
      numbers.split(",").map(it => (key, it))
    }).collect()
    println(result1.toBuffer)   --ArrayBuffer((A,1), (A,2), (A,3), (B,3), (B,4), (C,6), (C,7))
  }
}

2   产生shuffle的算子(宽依赖)

2.1  groupBy 和 groupByKey(将指定元素或者key相同的放在一起/一组,先局部放一起,再全局放一起)

groupBy : 根据RDD的某个指定的元素进行分组 ,相同的指定的元素会被分到一组,返回新的RDD

groupByKey : 根据RDD的key进行分组 ,key相同的会被分到一组 ,返回新的RDD

需求:按照统计单词出现的次数
1) 使用spark自带的groupByKey和 mapValues算子
2) 使用reduceByKey算子实现按key分组 ,相同key按照value聚合的功能
3) 不使用groupByKey ,而是使用combineByKey实现groupByKey的分组功能,然后在使用mapValues算子将key相同的value聚合
4) 不使用groupByKey ,而是使用自己new shuffleRDD 实现groupByKey的分组功能,然后在使用mapValues算子将key相同的value聚合

object GroupByKey02 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val lines = sc.textFile(args(0))

    val wordAndOne: RDD[(String, Int)] = lines.flatMap(_.split(" ")).map((_, 1))

   -----------第一种方法 : groupByKey + mapValues + sum 实现单词出现次数统计---------------
    --将新获得的RDD按照key进行分组
    val grouped: RDD[(String, Iterable[Int])] = wordAndOne.groupByKey()
    val grouped1: RDD[(String, Iterable[Int])] = new PairRDDFunctions[String, Int](wordAndOne).groupByKey()
    --分组后,将key相同的value聚合起来
    val sum: RDD[(String, Int)] = grouped.mapValues(it => it.sum)
    val sum1: RDD[(String, Int)] = grouped1.mapValues(it => it.sum)
    --sum.collect().foreach(println)    --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
    --sum1.collect().foreach(println)   --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)

    -----------第二种方法 : reduceByKey--------------------------------------------------
    --以上groupByKey+mapValues 的方法,如果数据较多时,因为groupByKey没有先局部聚合,会有更多的shuffle
    --处理数据时效率低,用reduceByKey效果会稍微好点 ,根据key分组,key相同的会被分到一组,然后按照value进行聚合
    val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    --reduced.collect().foreach(println)   --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)

    -----------第三种方法 : combineByKey+ mapValues + sum----实现groupByKey相同的功能-----
    --使用combineByKey实现groupByKey相同的功能
    val f1 = (v: Int) => ArrayBuffer(v) --将key的value值放到数组里面
    val f2 = (arr: ArrayBuffer[Int], v: Int) => arr += v --将key相同的value值添加到数组里面
    val f3 = (arr: ArrayBuffer[Int], arr1: ArrayBuffer[Int]) => arr ++= arr1 --将key相同的两个数组合并起来
    --这里使用的是ArrayBuffer ,没有使用CombineBuffer高效(因为是private的),因为ArrayBuffer中的数据都在磁盘里面了
    val grouped2: RDD[(String, ArrayBuffer[Int])] = wordAndOne.combineByKey(f1, f2, f3, new HashPartitioner(wordAndOne.partitions.length), false)
    --grouped2.mapValues(_.sum).collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)

    -----------第四种方法 : new ShuffleRDD---------实现groupByKey相同的功能-------------
    --自己new ShuffleRDD 实现groupByKey相同的功能
    val shuffledRDD: ShuffledRDD[String, Int, ArrayBuffer[Int]] = new ShuffledRDD[String, Int, ArrayBuffer[Int]](wordAndOne, new HashPartitioner(wordAndOne.partitions.length))
    shuffledRDD.setAggregator(new Aggregator[String, Int, ArrayBuffer[Int]](f1, f2, f3))
    shuffledRDD.setMapSideCombine(false)
    shuffledRDD.mapValues(_.sum).collect().foreach(println) --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
  }
}

2.2   reduceByKey

根据RDD的key值进行分组 ,相同key的RDD被分到一组 ,按照value值先局部聚合相加,再全局聚合相加,底层调用的是combineByKeyWithClassTag,并在方法中中new的ShuffleRDD

object ReduceByKey01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val lines: RDD[String] = sc.textFile(args(0))

    val wordAndOne: RDD[(String, Int)] = lines.flatMap(_.split(" ")).map((_, 1))
    ---------第一种方法 : reduceByKey------------------
    val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    reduced.collect().foreach(println)    --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)

    ---------第二种方法 : 使用combineByKey实现与reduceByKey 相同的功能-------------
    val f1 =(v:Int) => v         --得到key的value值
    val f2 =(v1:Int ,v2:Int) => v1 + v2     --相同的key, 其value值进行局部聚合/累加
    val f3 =(v3:Int, v4:Int) => v3 + v4     --相同的key, 其value值进行全部聚合/累加
    mapSideCombine :true ,指在map端进行聚合操作 ,但不影响最终reduce端聚合的结果
    val reduced1: RDD[(String, Int)] = wordAndOne.combineByKey(f1, f2, f3, new HashPartitioner(wordAndOne.partitions.length), true)
    reduced1.collect().foreach(println)  --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)

    --------第三种方法 : 自己new shuffleRDD 实现与reduceByKey相同的功能---------
    val shuffleRDD: ShuffledRDD[String, Int, Int] = new ShuffledRDD[String, Int, Int](wordAndOne, new HashPartitioner(wordAndOne.partitions.length))
    shuffleRDD.setAggregator(new Aggregator[String,Int,Int](f1,f2,f3))
    shuffleRDD.setMapSideCombine(true)
    shuffleRDD.collect().foreach(println)  --结果为 : (javaee,3)(hive,1)(spark,3)(hbase,4)
  }
}

2.3  distinct : 去重,先调用map将数据变成K,V类型,Value是null,然后调用reduceByKey,先在每个分区进行局部去重,然后在全局去重

object Distinct01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 1, 1, 2, 4, 3, 5), 2)
    --distinct : 去重的功能
    val dis: RDD[Int] = rdd1.distinct()
    println(dis.collect().toBuffer)    --- ArrayBuffer(4, 2, 1, 3, 5)

    --------不使用distinct算子 ,但是要实现去重的功能的方法--------------------
    val numAndNull: RDD[(Int, Null)] = rdd1.map(i => (i, null))
    --调用reduceByKey的方法聚合 ,将相同的元素聚合起来然后只取出一个
    val reduced: RDD[(Int, Null)] = numAndNull.reduceByKey((a, _) => a)
    --只要key
    val distincted: RDD[Int] = reduced.map(_._1)
    println(distincted.collect().toBuffer)    --- ArrayBuffer(4, 2, 1, 3, 5)
  }
}

2.4  aggregateByKey : 按照key进行聚合 , 指定一个初始值(初始值在局部聚合的时候使用 ,全局聚合的时候不再使用) ; 跟 reduceByKey 类似 , 可以输入两个参数 ,第一个函数是进行局部分区的操作/聚合 ,第二个函数是全局的数据操作/聚合

object AggregateByKey01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val list: List[(String, Int)] = List(("A",2),("B",3),("A",5),("C",6),("A",2),("B",3),("A",5),("C",6))
    val listRDD: RDD[(String, Int)] = sc.parallelize(list, 2)

    --aggregateByKey ,需要指定一个初始值 ,指定局部聚合函数和全局聚合函数 ;然后先局部聚合 ,再全局聚合 ;
    --与reduceByKey效果一样
    val reduced: RDD[(String, Int)] = listRDD.aggregateByKey(0)(_ + _, _ + _)
    println(reduced.collect().toBuffer)    --ArrayBuffer((B,6), (A,14), (C,12))

    --取出局部的最大值 ,然后将每个局部的最大值再相加
    val reduced1 = listRDD.aggregateByKey(0)(Math.max(_, _), _ + _)
    println(reduced1.collect().toBuffer)    --ArrayBuffer((B,6), (A,10), (C,12))

    --aggregateByKey : 指定的初始值可以是任意数值 ,但初始值只在局部聚合的时候使用 ,全局聚合的时候就不会再使用了
    val reduced2 = listRDD.aggregateByKey(100)(_ + _, _ + _)
    --第一个区局部聚合 : A :(100+2)+(100+5)=207    B:100+3=103   C:100+6=106
    --第二个区局部聚合 : A :(100+2)+(100+5)=207    B:100+3=103   C:100+6=106
    --全局聚合 : A :207+207=214     B:103+103=206   C:106+106=212
    println(reduced2.collect().toBuffer)   --ArrayBuffer((B,206), (A,214), (C,212))
  }
}

2.5  flodByKey : 先局部聚合 ,再全局聚合(底层调用的是combineByKeyWithClassTag, 并在方法中中new的ShuffleRDD)

object AggregateByKey01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val list: List[(String, Int)] = List(("A",2),("B",3),("A",5),("C",6),("A",2),("B",3),("A",5),("C",6))
    val listRDD: RDD[(String, Int)] = sc.parallelize(list, 2)

    --flodByKey :与reduceByKey 的功能相似
    val reduced3: RDD[(String, Int)] = listRDD.foldByKey(0)(_ + _)
    println(reduced3.collect().toBuffer)    -- ArrayBuffer((B,6), (A,14), (C,12))
  }
}

2.6  combineByKey :底层调用的是combineByKeyWithClassTag, 并在方法中中new的ShuffleRDD

需要输入三个参数 :

第一个参数为分组后 value 的第一个元素 ;

第二个函数为局部聚合函数 ;

第三个函数为全局聚合函数 .

import scala.collection.mutable.ArrayBuffer

object CombineByKey01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd1: RDD[String] = sc.parallelize(List("A", "B", "C", "D", "E", "F", "G", "H"),3)
    val rdd2: RDD[Int] = sc.parallelize(List(1, 2, 1, 1, 1, 2, 2, 2), 3)
    ---拉链操作 ,将两个RDD两类关联 ,变成对偶元组(zip 操作的两个RDD的分区数必须要一致)-----
    val ziped: RDD[(Int, String)] = rdd2.zip(rdd1)
    println(ziped.collect().toBuffer)   --ArrayBuffer((1,A), (2,B), (1,C), (1,D), (1,E), (2,F), (2,G), (2,H))

    --使用combineByKey算子 ,将key相同的value收集起来
    val f1 = (v:String) => ArrayBuffer(v)
    val f2 = (arr:ArrayBuffer[String] ,v:String) => arr += v
    val f3 = (arr1:ArrayBuffer[String] ,arr2:ArrayBuffer[String]) => arr1 ++= arr2
    val reduced = ziped.combineByKey(f1, f2, f3)
    println(reduced.collect().toBuffer)  
    --结果为 : ArrayBuffer((1,ArrayBuffer(A, C, D, E)), (2,ArrayBuffer(B, F, G, H)))
  }
}

2.7  cogroup :协分组 ,跟fullOuterJoin有点类似 ,但是没有关联上的返回 CompactBuffer()

1) 可以将两个或者多个RDD合并到一起后再进行分组 ,要求是 : 合并的RDD的 key 数据类型必须要一致 ,value值的类型可以不一致

2) 可以自己cogroup自己 ,也可以cogroup多个RDD ,但一个RDD最多可以cogroup 3个RDD ,再多久只能在 cogroup一次 

与group的区别 : group 只能对一个RDD进行分组

object CoGroup01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("CoGroup01").setMaster("local[*]")
    --创建 SparkContext
    val sc = new SparkContext(conf)
    --使用sc 创建原始的RDD
    val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 3), ("c", 2)), 2)
    val rdd2: RDD[(String, Int)] = sc.parallelize(List(("b", 2), ("c", 1)), 2)

    --cogroup 是将两个或多个RDD合并到一起再进行分组[与group的区别是 ,group只能是对一个RDD进行分组,cogroup是对多个RDD进行分组]
    val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
    --cogroup 之后得到的是一个元组 ,相同的key被分到一组 ,value 是一个个迭代器 (抓取RDD 的value值),
    val result: Array[(String, (Iterable[Int], Iterable[Int]))] = cogrouped.collect()
    result.foreach(println(_))  --(b,(CompactBuffer(3),CompactBuffer(2))) (a,(CompactBuffer(1),CompactBuffer())) (c,(CompactBuffer(2),CompactBuffer(1)))

    --cogroup实现Wordcount 功能,但是不推荐 ,因为效率低[多个RDD进行聚合到一起 ,先uinon ,再reduceByKey]
    val wcRes: RDD[(String, Int)] = cogrouped.mapValues(t => (t._1.sum + t._2.sum))
    wcRes.collect().foreach(println)    --(b,5) (a,1)  (c,3)    


    --不仅可以cogroup对两个RDD,还可以对多个RDD进行cogroup ,甚至可以自己cogroup自己---------
    -------可以自己 cogroup 自己--------------
    val cogrouped1: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd1)
    cogrouped1.collect().foreach(println)  //(b,(CompactBuffer(3),CompactBuffer(3)))  (a,(CompactBuffer(1),CompactBuffer(1))) (c,(CompactBuffer(2),CompactBuffer(2)))

    -----可以 cogroup 多个RDD ,但最多只能cogroup 3个RDD,在多只能再 cogroup 一次----------
    --再建一个原始的RDD
    val rdd3: RDD[(String, Int)] = sc.parallelize(List(("c", 7)), 2)
    val cogrouped2: RDD[(String, (Iterable[Int], Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2, rdd3)
    cogrouped2.collect().foreach(println(_))
     结果为 :
     (b,(CompactBuffer(3),CompactBuffer(2),CompactBuffer()))
     (a,(CompactBuffer(1),CompactBuffer(),CompactBuffer()))
     (c,(CompactBuffer(2),CompactBuffer(1),CompactBuffer(7)))
     
    --释放资源
    sc.stop()
  }
}

2.8  intersection (交集 : 取两个RDD 中都有的元素) 和 subtract (差集) 和 substractByKey (根据key取差集)

intersection : 交集 ,取两个RDD 中都有的元素
object Intersection01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Intersection01").setMaster("local[*]")
    val sc = new SparkContext(conf)
    --创建原始的RDD
    val rdd1: RDD[String] = sc.parallelize(List("a", "b", "c"), 2)
    val rdd2: RDD[String] = sc.parallelize(List("a", "c", "f"), 2)

    ---取两个RDD的交集 ,并且有去重效果
    val intersectioned: RDD[String] = rdd1.intersection(rdd2)
    intersectioned.collect().foreach(println)   -- a  c      

    -----------不使用 intersection 方法也可以实现取交集的方法---------------
    val rdd11: RDD[(String, Null)] = rdd1.map(s => (s, null))
    val rdd22: RDD[(String, Null)] = rdd2.map(s => (s, null))

    --调用 cogroup 方法,将这两个RDD合并到一起
    val cogrouped: RDD[(String, (Iterable[Null], Iterable[Null]))] = rdd11.cogroup(rdd22)
    --出结果看出 ,相同key被合并到一起,在哪个Rdd 中存在 ,就在其 value值位置被赋值null ,只有value值都被赋值null的说明在两个RDD中都存在
    cogrouped.collect().foreach(println)   --(b,(CompactBuffer(null),CompactBuffer())) (f,(CompactBuffer(),CompactBuffer(null)))...

    --即将交集取出来[相同的key在第一个RDD中出现 ,并且也在第二个迭代器中出现,即两个迭代器都不是空迭代器,说明两个RDD中都有值]
   ---------第一种过滤形式---------
    val result: RDD[String] = cogrouped.filter(t => t._2._1.nonEmpty && t._2._2.nonEmpty).keys
    result.collect().foreach(println)    --a  c

    -------第二中过滤形式----------
    val result1: RDD[String] = cogrouped.filter { case (_, (t1, t2)) => t1.nonEmpty && t2.nonEmpty }.keys
    println(result1.collect().toBuffer)   --ArrayBuffer(a, c)

    --如果想让线程不结束 ,可以让其睡一会
    Thread.sleep(10000000)
    --或者直接释放资源
    sc.stop()
  }
}
object Substract01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Sbstract01").setMaster("local[*]")
    val sc = new SparkContext(conf)
    --创建原始的RDD
    val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 3, 4), 3)
    val rdd2: RDD[Int] = sc.parallelize(List(4, 5, 6, 7, 8), 2)
    --取差集 ,取rdd1 中与rdd2 不相同的元素
    rdd1.subtract(rdd2).collect().foreach(println)   ----- 3 3 1 2
    --取差集 ,取rdd2 中与rdd1 不相同的元素
    rdd2.subtract(rdd1).collect().foreach(println(_))   ----- 6 8 5 7


    --创建原始的RDD
    val rdd1: RDD[(String, Int)] = sc.parallelize(List(("t", 1), ("j", 2), ("k", 3), ("k", 4)))
    val rdd2: RDD[(String, Int)] = sc.parallelize(List(("j", 9), ("t", 8), ("s", 7),("t",2)))

    --根据key求两个RDD 的差集,只要rdd1中有与rdd2 中 key 相同的,就都移除掉 ,只留下rdd1中与rdd2中key不相同的元素
    val rdd3: RDD[(String, Int)] = rdd1.subtractByKey(rdd2)
    rdd3.collect().foreach(println(_))        -----(k,3) (k,4)
  }
}

2.9  join(连接) ,leftOuterJoin(左外连接) ,rightOuterJoin(右外连接) ,fullOuterJoin(全外连接) :底层调用的都是 cogroup 方法

join : 

object InnerJoin01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("InnerJoin01").setMaster("local[*]")
    --创建sparkcontext
    val sc: SparkContext = new SparkContext(conf)
    --创建原始RDD
    val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 3)))
    val rdd2: RDD[(String, Int)] = sc.parallelize(List(("b", 2), ("a", 4), ("c", 1)))
    --使用join 方法---根据key相同取两个RDD的交集 ,没有去重效果
    val result: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
    result.collect().foreach(println)     -----(a,(1,4)) (b,(3,2))

    // rdd1.intersection(rdd2).collect().foreach(println)   --intersection 返回的是一个单个元素的RDD,元组形式的不能使用

    ---------不使用join ,但是需要实现join相同的功能------------
    val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
    --对元组的value值进行处理(过滤 ,只有两个迭代器都不为空 ,才将 cogrouped 打印出来)
    val keyAndValues: RDD[(String, (Int, Int))] = cogrouped.flatMapValues(t => {
      --for循环遍历 ,两个迭代器都有值才会进行到 yield
      for (v <- t._1.iterator; w <- t._2.iterator) yield (v, w)
    })
    keyAndValues.collect().foreach(println)   --(a,(1,4)) (b,(3,2))
    Thread.sleep(1000000)
  }
}

leftOuterJoin :

object LeftOuterJoin01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("LeftOuterJoin01").setMaster("local[*]")
    --创建一个 sc
    val sc = new SparkContext(conf)
    --创建两个原始的RDD
    val rdd1: RDD[(String, Int)] = sc.parallelize(List(("TOM", 2), ("HI", 3), ("WO", 5)))
    val rdd2: RDD[(String, Int)] = sc.parallelize(List(("HI", 4), ("KEY", 3), ("TOM", 5)))
    ----------join--------------
    val joined: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
    println(joined.collect().toBuffer)     -- ArrayBuffer((HI,(3,4)), (TOM,(2,5)))
    ----------leftOuterJoin-----------
    val leftjoined: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)
    println(leftjoined.collect().toBuffer)    -- ArrayBuffer((WO,(5,None)), (HI,(3,Some(4))), (TOM,(2,Some(5))))   

    --------------不使用leftOuterJoin也能实现其功能的方法------------
    val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
    将value压平
    val leftOuterJoined: RDD[(String, (Int, Option[Int]))] = cogrouped.flatMapValues(t => {
      if (t._2.isEmpty) {
        t._1.iterator.map(v => (v, None))
      } else {
        for (v1 <- t._1.iterator; v2 <- t._2.iterator) yield (v1, Some(v2))
      }
    })
    println(leftOuterJoined.collect().toBuffer)    --ArrayBuffer((WO,(5,None)), (HI,(3,Some(4))), (TOM,(2,Some(5))))
  }
}

rightOuterJoin :

object RightOuterJoin01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("RightOuterJoin01").setMaster("local[*]")
    --创建 sc
    val sc: SparkContext = new SparkContext(conf)
    --创建原始的RDD
    val rdd1: RDD[(String, Int)] = sc.parallelize(List(("TOM", 2), ("YUU", 1), ("KEY", 4)))
    val rdd2 = sc.parallelize(List(("TOM", 2), ("hi", 1), ("KEY", 4)))

    --使用rightouterjoin方法 ,得到以右表为主的新的RDD
    val rigthjoined: RDD[(String, (Option[Int], Int))] = rdd1.rightOuterJoin(rdd2)
    val tuples: Array[(String, (Option[Int], Int))] = rigthjoined.collect()
    tuples.foreach(println(_))       --(hi,(None,1))  (TOM,(Some(2),2))  (KEY,(Some(4),4))
    println(tuples.toBuffer)         --ArrayBuffer((hi,(None,1)), (TOM,(Some(2),2)), (KEY,(Some(4),4)))

    -----------------不使用rightOuterJoin 方法也可以实现其功能----------------
    val cogrouped: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
    --对元组的value值进行处理 ,但是以右边表为主
    val result: RDD[(String, (Option[Int], Int))] = cogrouped.flatMapValues(t => {
      if (t._1.isEmpty) {
        t._2.iterator.map(v2 => (None, v2))
      } else {
        for (v1 <- t._1.iterator; v2 <- t._2.iterator) yield (Some(v1), v2)
      }
    })
    result.collect().foreach(println)   ---(hi,(None,1)) (TOM,(Some(2),2)) (KEY,(Some(4),4))

    Thread.sleep(1000000)
  }
}

2.10  cartesian : 笛卡尔积 (不推荐使用 ,产生的数据量太大 ,占空间 ,浪费资源 ,如果数据量很小的话还可以使用)

object cartesian01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd1: RDD[String] = sc.parallelize(List("tom", "jim"))
    val rdd2: RDD[String] = sc.parallelize(List("tom", "jon", "shuke"))
    val result: RDD[(String, String)] = rdd1.cartesian(rdd2)
    println(result.collect().toBuffer)
    
    结果为 :ArrayBuffer((tom,tom), (tom,jon), (tom,shuke),
                       (jim,tom), (jim,jon), (jim,shuke)) 
    
    sc.stop()
  }
}

3  RDD的Transformation算子实例操作(计算订单分类成交金额)

需求 : 在给定的订单数据,根据订单的分类ID进行聚合,然后按照订单分类名称,统计出某一天商品各个分类的成交金额

3.1 数据信息

--数据信息
{"cid": 1, "money": 600.0, "longitude":116.397128,"latitude":39.916527,"oid":"o123", }
"oid":"o112", "cid": 3, "money": 200.0, "longitude":118.396128,"latitude":35.916527}
{"oid":"o124", "cid": 2, "money": 200.0, "longitude":117.397128,"latitude":38.916527}
{"oid":"o125", "cid": 3, "money": 100.0, "longitude":118.397128,"latitude":35.916527}
{"oid":"o127", "cid": 1, "money": 100.0, "longitude":116.395128,"latitude":39.916527}
{"oid":"o128", "cid": 2, "money": 200.0, "longitude":117.396128,"latitude":38.916527}
{"oid":"o129", "cid": 3, "money": 300.0, "longitude":115.398128,"latitude":35.916527}
{"oid":"o130", "cid": 2, "money": 100.0, "longitude":116.397128,"latitude":39.916527}
{"oid":"o131", "cid": 1, "money": 100.0, "longitude":117.394128,"latitude":38.916527}
{"oid":"o132", "cid": 3, "money": 200.0, "longitude":118.396128,"latitude":35.916527}

--分类信息
1,家具
2,手机
3,服装

3.2  将计算结果保存到MySQL中 (dt,category,money)

3.3  在java的pom.xml文件中导入Maven依赖

<!-- 解析JSON的依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.57</version>
</dependency>

<!-- mysql连接依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

3.4  代码实现需求程序

3.4.1  创建一个 case class ,将待处理的数据信息各个字段信息输入到case class中

case class OrderBean(
                      oid:String,
                      cid:String,
                      money:Double ,
                      longitude:Double,
                      latitude:Double)

3.4.2  创建一个object类 ,在里面写数据处理和计算分析逻辑代码

object Order01 {
  private val logger = LoggerFactory.getLogger(this.getClass)

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Order").setMaster("local[*]")
    val sc = new SparkContext(conf)
    --读取文件 ,获得数据
    val lines: RDD[String] = sc.textFile(args(0))

    --将 lines 数据进行转化
    val beanRDD: RDD[OrderBean] = lines.map(line => {
      --解析JSON数据 ,将解析后的数据封装到 orderbean 中
      --因为json数据有脏数据 ,需要进行 try catch 捕获脏数据,不然程序一遇到脏数据就会停止运行
      var bean: OrderBean = null
      try {
        bean = JSON.parseObject(line, classOf[OrderBean])
      } catch {
        case e: JSONException => {
          --记录错误数据
          logger.error("parse json error =>" + line)   --捕获到的脏数据 : "o112", "cid": 3, "money": 200.0, "longitude":118.396128,"latitude":35.916527}
        }
      }
      --返回bean
      bean
    })
    --过滤有问题的数据
    val filtered = beanRDD.filter(_ != null)
    --将没有问题的数据组装成元组 ,然后进行分组聚合
    val cidAndMoney: RDD[(String, Double)] = filtered.map(b => (b.cid, b.money)).reduceByKey(_ + _)

    --将分类的数据 并行 化成RDD
    val category: RDD[(String, String)] = sc.parallelize(List(("1", "家具"), ("2", "手机"), ("3", "服装")))
    --将两个 RDD 关联起来  1     钱     家具 ,关联的字段名字和类型必须是一致的
    val joined: RDD[(String, (Double, String))] = cidAndMoney.join(category)
    --从关联的结果中获取  种类和对应的钱
    val categoryAndMoney = joined.map(m => (m._2._2, m._2._1))
    --将结果打印出来
   --println(categoryAndMoney.collect().toBuffer)      --ArrayBuffer((手机,500.0), (服装,600.0), (家具,800.0))


    --将结果发送到 mysql 中
    --调用一个 action 算子
    categoryAndMoney.foreachPartition(it=>{
      --在Navicat中开启一个连接 ,然后建立一个 db_data 数据库 ,在该数据库中建立一个tb_daily_category_income表格
      --在该表格中有以下查询的几个字段 : dt,category,money ,这些字段的数据类型与查询指定类型一样 ,且字段的名字也要是一模一样的
      val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_data?characterEncoding=UTF-8", "root", "123456")
      val statement = connection.prepareStatement("insert into tb_daily_category_income(dt,category,money) values(?,?,?)")
      it.foreach(t=>{
        --查询数据处理出结果的当天日期
        statement.setDate(1,new Date(System.currentTimeMillis()))
        --查询的category 字段结果
        statement.setString(2,t._1)
        --查询出的 money 结果
        statement.setDouble(3,t._2)
        --这个方法是用一个链接,将数据"批量写入"到mysql中, 效率高(非常适合数据量大的时候使用)
        statement.executeLargeUpdate()   
      })
      --数据写完后 ,关流/通道/链接
      statement.close()
      connection.close()
    })
    --释放 sc 资源
    sc.stop()
  }
}

导入到数据库中的结果如下 : 

将数据写入到mysql中的简约原理图 : 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值