Spark SQL编程之RDD-RDD转换

背景

本文使用idea编程

spark 版本

2.11.8
2.2.0
2.11

 备注

Spark中,只有遇到action,才会执行RDD的计算(即延迟计算)

RDD创建

创建方式 

  1. 从集合中创建RDD
  2. 从外部存储创建RDD
  3. 从其他RDD创建

  def testCreate(spark: SparkSession) = {
    val sc: SparkContext = spark.sparkContext
    // 使用makeRDD从集合中创建
    val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 2), (2, 3), (3, 4), (4, 5)))

    // 使用parallelize从集合中创建
    val rdd2: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5))

    // 从外部文件中获取(还可以从各种数据库、HDFS等等外部资源中获取)
    val rdd3: RDD[String] = sc.textFile("***/RddDemo.scala")

    //从rdd2中获取新的rdd
    val rdd4: RDD[Int] = rdd2.map(x => x + 2)
  }

 RDD转换

Value类型交互

 map(fun)

作用:返回一个新的rdd,每个元素根据fun函数进行映射

    // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    val rdd1: RDD[Int] = spark.sparkContext.parallelize(1 to 10)
    //     可以简写成这个样子rdd1.map(_ + 2)
    //List(3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
    val rdd2: RDD[Int] = rdd1.map(x => x + 2)
        

 mapPartitions(fun)

作用:类似map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,fun的函数类型必须是Iterator[T] => Iterator[U]

PS:假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区

    //List(3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
    val rdd3: RDD[Int] = rdd1.mapPartitions(x => x.map(e => e + 2))

map()和 mapPartitions()的区别

  1. map 每次只处理一条数据
  2. mapPartition每次处理一个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才释放,容易导致OOM
  3. 当内存足够大的时候建议使用mapPartition,可以提升效率

 mapPartitionsWithIndex(fun)

作用:类似于mapPartitions,但fun带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,fun的函数类型必须是(Int, Interator[T]) => Iterator[U]

PS:index不会超过分片个数,如果没有指定分片的话,系统有默认分片

    //List((0,1), (1,2), (2,3), (3,4), (3,5), (4,6), (5,7), (6,8), (7,9), (7,10))
    val rdd4: RDD[(Int, Int)] = rdd1.mapPartitionsWithIndex((index, x) => x.map(e => (index, e)))

 flatMap(fun)

作用:扁平化映射,每个元素可以映射成0活多个元素(所以函数输出的是一个序列)

    //List(-2, -1, 0, 1, -1, 0, 1, 2, 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6, 4, 5, 6, 7, 5, 6, 7, 8, 6, 7, 8, 9, 7, 8, 9, 10)
    val rdd5: RDD[Int] = rdd1.flatMap(x => x - 3 to x)

 glom()

作用:将每一个分区形成一个数组,形成新的RDD类型时RDD[Array[T]]

PS:如果rdd中没有指定分片个数,系统会有默认的分片个数(根据系统资源默认获取)

    //List(List(3), List(4, 5), List(7), List(1), List(6), List(2), List(9, 10), List(8))
    // 没有指定指定分片数量
    val rdd6: RDD[Array[Int]] = rdd1.glom()
    
    val r1: RDD[Int] = sc.parallelize(1 to 10, 3)
    //List(List(1, 2, 3), List(7, 8, 9, 10), List(4, 5, 6))
    // 指定分片数量
    val rdd61: RDD[Array[Int]] = r1.glom()

 groupBy(fun)

作用:分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器

    //List((0,CompactBuffer(3, 6, 9)), (1,CompactBuffer(1, 4, 7, 10)), (2,CompactBuffer(2, 5, 8)))
    //每个元素对3取余分组
    val rdd7: RDD[(Int, Iterable[Int])] = rdd1.groupBy(x => x % 3)

filter(fun)

作用:过滤。返回一个新的RDD,该RDD由经过fun函数计算返回值为true的输入元素组成

    //List(2, 4, 6, 8, 10)
    //求全部偶数集合
    val rdd8: RDD[Int] = rdd1.filter(x => x % 2 == 0)

 distinct([numTasks]))

作用:对源RDD进行去重后返回一个新的RDD。默认情况下,只有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它

PS: 指定不同的并行度得到的数据顺序可能不一致

    val distinctRdd: RDD[Int] = sc.makeRDD(Array(1, 1, 2, 2, 3, 3, 4, 4, 5))
    // 可以指定并行度 得到的结果顺序可能不一致
    // List(1, 2, 3, 4, 5)
    val rdd9: RDD[Int] = distinctRdd.distinct()
    // List(3, 4, 1, 5, 2)
    val rdd91: RDD[Int] = distinctRdd.distinct(3)

sortBy(func,[ascending], [numTasks]) 

作用:使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序

    //排序 -倒序
    val sorted1: RDD[Int] = rdd1.sortBy(x => x, false, 4)
    println("操作后数据: " + sorted1.collect().toList)

    val s1: RDD[(Int, Int, Int)] = sc.makeRDD(Array((1, 2, 6), (6, 5, 3), (1, 2, 3), (9, 6, 6), (6, 7, 3)))
    // 按照第三个参数 第二个参数 第一个参数顺序排序
    val sorted2: RDD[(Int, Int, Int)] = s1.sortBy(x => (x._3, x._2, x._1), false)

    val sorted3: Array[(Int, Int, Int)] = s1.top(10)(Ordering[(Int, Int, Int)].on(x => (x._3, x._2, x._1)))

//整数排序:1: List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
//多参数排序2: List((9,6,6), (1,2,6), (6,7,3), (6,5,3), (1,2,3))
//多参数排序3: List((9,6,6), (1,2,6), (6,7,3), (6,5,3), (1,2,3))

 coalesce(numPartitions)

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

 repartition(numPartitions)

作用:根据分区数,重新通过网络随机洗牌所有数据

    val r2: RDD[Int] = sc.parallelize(1 to 10, 4)
    val rdd10: RDD[Int] = r2.coalesce(1)
    println("之前分区数量: " + r2.partitions.length + " ;调整之后:" + rdd10.partitions.length)
    val rdd11: RDD[Int] = r2.repartition(3)
    println("之前分区数量: " + r2.partitions.length + " ;调整之后:" + rdd11.partitions.length)
//之前分区数量: 4 ;coalesce调整之后:1
//之前分区数量: 4 ;repartition调整之后:3

 区别和联系

  1. repartition实际上是调用的coalesce,默认是进行shuffle的
  2. coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定 

Spark SQL编程之RDD-RDD转换_第1张图片

 sample(withReplacement, fraction, seed)

作用:以指定的随机种子随机抽样出数量为fraction的数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样,seed用于指定随机数生成器种子

  //放回抽样
    val rdd12: RDD[Int] = rdd1.sample(true, 0.5, 2)
    println("放回抽样:" + rdd12.collect().toList)
    val rdd13: RDD[Int] = rdd1.sample(false, 0.5, 2)
    println("不放回抽样: " + rdd13.collect().toList)
//放回抽样:List(3, 5, 7, 9, 9)
//不放回抽样: List(2, 3, 5, 8, 10)

pipe(command, [envVars])

作用:管道,针对每个分区,都执行一个shell脚本,返回输出的RDD。

注意:脚本需要放在Worker节点可以访问到的位置

    // 可以是编译器内的绝对路径,window环境不能执行 linux的shell脚本
    val rdd14: RDD[String] = rdd1.pipe("***\\pip.bat")
    println("管道执行:" + rdd14.collect().toList)

 双Value类型交互

union(otherDataset) 

作用:【并集】对源RDD和参数RDD求并集后返回一个新的RDD

 subtract (otherDataset)

作用:【差集】计算差的一种函数,去除两个RDD中相同的元素,不同的RDD将保留下来

 intersection(otherDataset)

作用:【交集】对源RDD和参数RDD求交集后返回一个新的RDD

 zip(otherDataset)

作用:【拉链】将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常

 cartesian(otherDataset)

作用:【笛卡尔积】尽量避免使用

 def testTransferDoubleValue(spark: SparkSession) = {
    val sc: SparkContext = spark.sparkContext
    val rdd1: RDD[Int] = sc.parallelize(1 to 4)
    val rdd2: RDD[Int] = sc.parallelize(2 to 5)
    //求并集
    val union: RDD[Int] = rdd1.union(rdd2)
    println("数据源1: " + rdd1.collect().toList)
    println("数据源2: " + rdd2.collect().toList)
    println("union(): " + union.collect().toList)
    //差集
    val subtract: RDD[Int] = rdd1.subtract(rdd2)
    println("subtract(): " + subtract.collect().toList)
    //交集
    val intersection: RDD[Int] = rdd1.intersection(rdd2)
    println("intersection(): " + intersection.collect().toList)
    //笛卡尔积
    val cartesian: RDD[(Int, Int)] = rdd1.cartesian(rdd2)
    println("cartesian(): " + cartesian.collect().toList)
    //zip 两个rdd的元素个数及分片个数必须是一样的,否则会报错
    val zip: RDD[(Int, Int)] = rdd1.zip(rdd2)
    println("zip(): " + zip.collect().toList)
  }
// 数据源1: List(1, 2, 3, 4)
// 数据源2: List(2, 3, 4, 5)
// union(): List(1, 2, 3, 4, 2, 3, 4, 5)
// subtract(): List(1)
// intersection(): List(2, 3, 4)
// cartesian(): List((1,2), (1,3), (1,4), (1,5), (2,2), (2,3), (2,4), (2,5), (3,2), (3,3), (3,4), (3,5), (4,2), (4,3), (4,4), (4,5))
// zip(): List((1,2), (2,3), (3,4), (4,5))

 Key-Value类型交互

 groupByKey() 

作用:groupByKey也是对每个key进行操作,但只生成一个sequence

    val sc: SparkContext = spark.sparkContext
    val rdd1: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 1), ("AA", 13), ("BB", 2), ("BB", 5), ("CC", 7)))
    //分组之后求key对应的数量 指定分片数量之后输出的顺序或会改变
    val groupRdd: RDD[(String, Iterable[Int])] = rdd1.groupByKey()
    //分组之后进行聚合
    val rdd2: RDD[(String, Int)] = groupRdd.map(x => (x._1, x._2.sum))
    // 可以使用分片参数
    val rdd21: RDD[(String, Int)] = rdd1.groupByKey(3).map(x => (x._1, x._2.sum))
    val rdd22: RDD[(String, Int)] = rdd1.groupByKey(new HashPartitioner(3)).map(x => (x._1, x._2.sum))
    println("分组求和: " + rdd2.collect().toList)
    println("分组求和: " + rdd21.collect().toList)
    println("分组求和: " + rdd22.collect().toList)

// 分组求和: List((BB,7), (CC,7), (AA,14))
// 分组求和: List((BB,7), (AA,14), (CC,7))
// 分组求和: List((BB,7), (AA,14), (CC,7))

reduceByKey(func, [numTasks])

作用:在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置 

    // 等同上一个计算的结果
    val rdd3: RDD[(String, Int)] = rdd1.reduceByKey((a, b) => (a + b))
    val rdd31: RDD[(String, Int)] = rdd1.reduceByKey((a, b) => (a + b), 3)
    val rdd32: RDD[(String, Int)] = rdd1.reduceByKey(new HashPartitioner(3), (a, b) => (a + b))
    println("根据key计算:" + rdd3.collect().toList)
    println("根据key计算:" + rdd31.collect().toList)
    println("根据key计算:" + rdd32.collect().toList)

// 根据key计算:List((BB,7), (CC,7), (AA,14))
// 根据key计算:List((BB,7), (AA,14), (CC,7))
// 根据key计算:List((BB,7), (AA,14), (CC,7))
// 结果和上一个分组求和完全一致

 reduceByKey和groupByKey的区别

  1. reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]
  2. groupByKey是按照key进行分组,直接进行shuffle(只是进行分组)
  3. reduceByKey性能较高优先使用

sortByKey([ascending], [numTasks]) 

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

    // 按照key的倒序排列
    val rdd4: RDD[(String, Int)] = rdd1.sortByKey(false)
    println("根据key计算:" + rdd4.collect().toList)

// 根据key计算:List((CC,7), (BB,2), (BB,5), (AA,1), (AA,13))

 join(otherDataset, [numTasks])

作用:在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD

    val j1: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 1), ("BB", 1), ("CC", 1), ("DD", 1)))
    val j2: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 2), ("BB", 2), ("CC", 2)))
    val j3: RDD[(String, (Int, Int))] = j1.join(j2, 3)
    println("形成新元组操作: " + j3.collect().toList)
// 形成新元组操作: List((BB,(1,2)), (AA,(1,2)), (CC,(1,2)))

 mapValues(fun)

作用:【只操作value】针对于(K,V)形式的类型只对V进行操作

val sc: SparkContext = spark.sparkContext

val rdd1: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 1), ("AA", 13), ("BB", 2), ("BB", 5), ("CC", 7)))    

val rdd5: RDD[(String, String)] = rdd1.mapValues(x => x + "->Value")
println("仅操作value: " + rdd5.collect().toList)

//仅操作value: List((AA,1->Value), (AA,13->Value), (BB,2->Value), (BB,5->Value), (CC,7->Value))

 combineByKey[C]

作用:对相同K,把V合并成一个集合

参数:(createCombiner: V => C,  mergeValue: (C, V) => C,  mergeCombiners: (C, C) => C)

1createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值

2mergeValue: 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并

3mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合并

    val sc: SparkContext = spark.sparkContext
    val rdd1: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 1), ("AA", 13), ("BB", 2), ("BB", 5), ("CC", 7)))
    val value: RDD[(String, (Int, Int))] = rdd1.combineByKey(i => (i, 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))
    println("结果: " + value.collect().toList)
//               List(key,(sum,count))  
// key联合计算: List((BB,(7,2)), (CC,(7,1)), (AA,(14,2)))

 aggregateByKey

作用:在kv对的RDD中,,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出

参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)

1zeroValue给每一个分区中的每一个key一个初始值;

2seqOp函数用于在每一个分区中用初始值逐步迭代value;

3combOp函数用于合并每个分区中的结果。

  foldByKey

作用:aggregateByKey的简化操作,seqop和combop相同

    val sc: SparkContext = spark.sparkContext
    val rdd1: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 1), ("AA", 13), ("BB", 2), ("BB", 10), ("BB", 5), ("CC", 7)))
    
    val rdd6: RDD[(String, Int)] = rdd1.aggregateByKey(0)((x, y) => math.max(x, y), (x, y) => x + y)
    println("key合计算: " + rdd6.collect().toList)

    val rdd7: RDD[(String, Int)] = rdd1.foldByKey(0)((x, y) => x + y)
    println("key合计算: " + rdd7.collect().toList)

// key合计算: List((BB,17), (CC,7), (AA,14))

cogroup(otherDataset, [numTasks])

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

val sc: SparkContext = spark.sparkContext    
val j1: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 1), ("BB", 1), ("BB", 3), ("CC", 1), ("DD", 1)))
val j2: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 2), ("BB", 2), ("CC", 2)))
val groupRdd1: RDD[(String, (Iterable[Int], Iterable[Int]))] = j1.cogroup(j2)
println("根据key进行分组:    " + groupRdd1.collect().toList)

// 根据key进行分组:    
//List((DD,(CompactBuffer(1),CompactBuffer())),
//     (BB,(CompactBuffer(1, 3),CompactBuffer(2))),
//     (CC,(CompactBuffer(1),CompactBuffer(2))),
//     (AA,(CompactBuffer(1),CompactBuffer(2))))

 Action操作

PS: 此时才真正的执行rdd内部的计算和聚合

 collect()

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

 count()

作用:返回RDD中元素的个数

 first()

作用:返回RDD中的第一个元素

take(n) 

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

takeOrdered(n)

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

 countByKey()

作用:针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数

 reduce(fun)

作用:通过func函数聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据

    val sc: SparkContext = spark.sparkContext
    val rdd1: RDD[(String, Int)] = sc.makeRDD(Array(("AA", 1), ("BB", 5), ("BB", 3), ("CC", 7), ("DD", 2)))
    println("collect(): " + rdd1.collect().toList)
    println("count(): " + rdd1.count())
    println("first(): " + rdd1.first())
    println("take(3): " + rdd1.take(3).toList)

    val countByKey: collection.Map[String, Long] = rdd1.countByKey()
    println("countByKey(): " + countByKey)

    // 输入的类型和输出的类型要一致
    val sum: Int = rdd1.values.reduce((x: Int, y: Int) => x + y)
    val sum1: (String, Int) = rdd1.reduce((x: (String, Int), y: (String, Int)) => (x._1 + "-" + y._1, x._2 + y._2))
    println("求和的结果: " + sum)
    println("求和的结果:+" + sum1)

    // 按照key的hashcode值为第一排序规则 value值倒序作为第二排序规则
    val takeOrdered: Array[(String, Int)] = rdd1.takeOrdered(3)(Ordering[(Int, Int)].on((x) => (-x._1.hashCode, -x._2)))
    println("原数据takeOrdered(3): " + takeOrdered.toList)

//  collect(): List((AA,1), (BB,5), (BB,3), (CC,7), (DD,2))
//  count(): 5
//  first(): (AA,1)
//  take(3): List((AA,1), (BB,5), (BB,3))
//  countByKey(): Map(DD -> 1, BB -> 2, CC -> 1, AA -> 1)
//  求和的结果: 18
//  求和的结果:+(DD-AA-BB-BB-CC,18)
//  原数据takeOrdered(3): List((DD,2), (CC,7), (BB,5))

 

aggregate 

作用:aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致

参数:(zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)

fold(num)(func)

作用:折叠操作,aggregate的简化操作,seqop和combop一样

    //1,2,3,4,5。6.7.8.9.10
    val rdd2: RDD[Int] = sc.parallelize(1 to 10)
    val aggregate: Int = rdd2.aggregate(0)((x, y) => x + y, (x, y) => x + y)
    println("aggregate(0): " + aggregate)
    val fold: Int = rdd2.fold(0)((x, y) => x + y)
    println("fold(0): " + fold)

//  输出
    // aggregate(0): 55
    // fold(0): 55

  foreach(fun)

作用:在数据集的每一个元素上,运行函数func进行更新

    val rdd2: RDD[Int] = sc.parallelize(1 to 10)
    rdd2.foreach(x => print(x + ", "))

  // 1, 3, 7, 6, 8, 2, 4, 5, 9, 10,

 saveAsTextFile(path)

作用:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本

saveAsObjectFile(path) 

 作用:用于将RDD中的元素序列化成对象,存储到文件中

    val rdd2: RDD[Int] = sc.parallelize(1 to 10)
    rdd2.saveAsTextFile("test.txt")
    rdd2.saveAsObjectFile("obj.txt")
//  可以指定文件生成路径,当前是生成在项目的根目录  

 

你可能感兴趣的:(高性能编程,spark,scala,RDD,SparkCore)