spark Transformation 算子

map(func)

通过函数func传递源的每个元素来形成一个新的分布式数据集

val arr=sc.parallelize(Array(("A",1),("B",2),("C",3)))
arr.map(x=>(x._1+x._2)).foreach(println)
print:   
A1
B2
C3

mapValues

mapValues顾名思义就是输入函数应用于RDD中Kev-Value的Value,原RDD中的Key保持不变,与新的Value一起组成新的RDD中的元素。因此,该函数只适用于元素为KV对的RDD。

scala> val a = sc.parallelize(List("dog", "tiger", "lion", "cat", "panther", " eagle"), 2)
scala> val b = a.map(x => (x.length, x))
scala> b.mapValues("x" + _ + "x").collect
res5: Array[(Int, String)] = Array((3,xdogx), (5,xtigerx), (4,xlionx),(3,xcatx), (7,xpantherx), (5,xeaglex))

mapWith

mapWith是map的另外一个变种,map只需要一个输入函数,而mapWith有两个输入函数。它的定义如下:

def mapWith[A: ClassTag, U: ](constructA: Int => A, preservesPartitioning: Boolean = false)(f: (T, A) => U): RDD[U]
第一个函数constructA是把RDD的partition index(index从0开始)作为输入,输出为新类型A;
第二个函数f是把二元组(T, A)作为输入(其中T为原RDD中的元素,A为第一个函数的输出),输出类型为U。
举例:把partition index 乘以10,然后加上2作为新的RDD的元素。

val x = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10), 3) 
x.mapWith(a => a * 10)((a, b) => (b + 2)).collect 
res4: Array[Int] = Array(2, 2, 2, 12, 12, 12, 22, 22, 22, 22)

flatMap(func):

与map类似,但每个元素输入项都可以被映射到0个或多个的输出项,最终将结果”扁平化“后输出

val arr=sc.parallelize(Array(("A",1),("B",2),("C",3)))
arr.flatmap(x=>(x._1+x._2)).foreach(println)
print:
A
1
B
2
C
3

flatMapWith

flatMapWith与mapWith很类似,都是接收两个函数,一个函数把partitionIndex作为输入,输出是一个新类型A;另外一个函数是以二元组(T,A)作为输入,输出为一个序列,这些序列里面的元素组成了新的RDD。它的定义如下:

def flatMapWith[A: ClassTag, U: ClassTag](constructA: Int => A, preservesPartitioning: Boolean = false)(f: (T, A) => Seq[U]): RDD[U]


scala> val a = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 3)
scala> a.flatMapWith(x => x, true)((x, y) => List(y, x)).collect
res58: Array[Int] = Array(0, 1, 0, 2, 0, 3, 1, 4, 1, 5, 1, 6, 2, 7, 2,
8, 2, 9)

flatMapValues

flatMapValues类似于mapValues,不同的在于flatMapValues应用于元素为KV对的RDD中Value。每个一元素的Value被输入函数映射为一系列的值,然后这些值再与原RDD中的Key组成一系列新的KV对。


scala> val a = sc.parallelize(List((1,2),(3,4),(3,6)))
scala> val b = a.flatMapValues(x=>x.to(5))
scala> b.collect
res3: Array[(Int, Int)] = Array((1,2), (1,3), (1,4), (1,5), (3,4), (3,5))

上述例子中原RDD中每个元素的值被转换为一个序列(从其当前值到5),比如第一个KV对(1,2), 其值2被转换为2,3,4,5。然后其再与原KV对中Key组成一系列新的KV对(1,2),(1,3),(1,4),(1,5)。

mapPartitions(func):

将map函数作用与每个分区,函数类型为:Iterator[T] => Iterator[U]
一般在数据库操作中用到


var rdd1 = sc.makeRDD(1 to 5,2)
//rdd1有两个分区
scala> var rdd3 = rdd1.mapPartitions{ x => {
     | var result = List[Int]()
     |     var i = 0
     |     while(x.hasNext){
     |       i += x.next()
     |     }
     |     result.::(i).iterator  //要求输入和返回都Iterator[U]类型,故将i转换为iterator
     | }}
rdd3: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[84] at mapPartitions at :23

//rdd3将rdd1中每个分区中的数值累加
scala> rdd3.collect
res65: Array[Int] = Array(3, 12)
scala> rdd3.partitions.size
res66: Int = 2

mapPartitionsWithIndex(func):

类似于mapPartitions,而且还提供FUNC与表示所述分区的索引的整数值,所以FUNC必须是类型(Int, Iterator[T]) => Iterator[U]

var rdd1 = sc.makeRDD(1 to 5,2)
//rdd1有两个分区
var rdd2 = rdd1.mapPartitionsWithIndex{
        (x,iter) => {
          var result = List[String]()
            var i = 0
            while(iter.hasNext){
              i += iter.next()
            }
            result.::(x + "|" + i).iterator

        }
      }
//rdd2将rdd1中每个分区的数字累加,并在每个分区的累加结果前面加了分区索引
scala> rdd2.collect
res13: Array[String] = Array(0|3, 1|12)

filter(func):

增加过滤器,针对一维的数据

val rdd = sc.parallelize(1 to 5)
rdd.filter(_>3).collect().foreach(println)
4
5

sample(withReplacement,fraction,seed):

指定的随机种子随机抽样
withReplacement:抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样
fraction :抽样的数量
seed:随机种子

val rdd = sc.parallelize(1 to 10)
val sample1 = rdd.sample(true,0.5,3)
sample1.collect.foreach(println)
9

union(ortherDataset):

将两个Rdd数据集合并返回

val rdd1 = sc.parallelize(1 to 3)
val rdd2 = sc.parallelize(3 to 5)
val unionRDD = rdd1.union(rdd2)
unionRDD.collect.foreach(println)
输出:
1
2
3
3
4
5

intersection(otherDataset):

返回两个Rdd的交集

val rdd1 = sc.parallelize(1 to 3)
val rdd2 = sc.parallelize(3 to 5)
val unionRDD = rdd1.intersection(rdd2)
unionRDD.collect.foreach(println)
输出:

3

distinct([ numTasks ])

对RDD数据集进行去重

val list = List(1,1,2,5,2,9,6,1)
val distinctRDD = sc.parallelize(list)
val unionRDD = distinctRDD.distinct()
unionRDD.collect.foreach(println)
1
9
5
6
2

groupByKey([ numTasks ])

当调用(K,V)对的数据集时,返回(K,Iterable )对的数据集。

一般优先选择reduceByKey,combineByKey,foldByKey

详情参照:
https://www.iteblog.com/archives/1357.html

val words = Array("one", "two", "two", "three", "three", "three")
val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))
wordPairsRDD.groupByKey().map(t => (t._1, t._2.sum)).collect().foreach(println)
输出:
(two,2)
(one,1)
(three,3)

reduceByKey(func,[ numTasks ])

当调用(K,V)对的数据集时,返回(K,V)对的数据集,其中每个键的值使用给定的reduce函数func进行聚合,函数func必须是(V,V)=> V.像in一样groupByKey,reduce任务的数量可以通过可选的第二个参数来配置。

val words = Array("one", "two", "two", "three", "three", "three")
val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))

val wordCountsWithReduce = wordPairsRDD
  .reduceByKey(_ + _)
  .collect().foreach(println)
输出:
(two,2)
(one,1)
(three,3)  

aggregate(zeroValue,seq,comb,taskNums)

将初始值和第一个分区中的第一个元素传递给seq函数进行计算,然后将计算结果和第二个元素传递给seq函数,直到计算到最后一个值。第二个分区中也是同理操作。最后将初始值、所有分区的结果经过combine函数进行计算(先将前两个结果进行计算,将返回结果和下一个结果传给combine函数,以此类推),并返回最终结果。

>>> data = sc.parallelize((1,2,3,4,5,6),2)
>>> def seq(a,b):
...     print 'seqOp:'+str(a)+"\t"+str(b)
...     return min(a,b)
... 
>>> def combine(a,b):
...     print 'comOp:'+str(a)+"\t"+str(b)
...     return a+b
... 
>>> data.aggregate(3,seq,combine)
seqOp:3  1
seqOp:1  2
seqOp:1  3
seqOp:3  4
seqOp:3  5
seqOp:3  6
comOp:3  1
comOp:4  3
7
  def main(args: Array[String]) {

    System.setProperty("hadoop.home.dir", "D:\\hadoop-2.6.4\\hadoop-2.6.4")

    val conf = new SparkConf().setAppName("Join").setMaster("local")
    val sc = new SparkContext(conf)

    val data = sc.parallelize(List((1, 3), (1, 2), (1, 4), (2, 3)),2)

    def seq(a:Int, b:Int): Int = {
      println("seq: " + a + "\t " + b)
      math.max(a, b)
    }

    def comb(a:Int,b:Int):Int={
      println("comb : "+a+"\t"+b)
      a+b
    }
    val result = data.aggregateByKey(0)(seq,comb)  //.collect()
    result.collect().foreach(println)

    print("==="+data.partitions.size)


  }

从上面的代码的输出结果可以看出,1,2,3被分到一个分区中,4,5,6被分到一个分区中。3先和第一个元素1传给seq函数,返回最小值1,然后将1和第二个元素2传给seq函数,返回1,以此类推,最后返回第一个分区中的最小值1。第二个分区一样道理,最后结果返回最小值3.最后将初始值3和两个分区的结果经过combine函数进行计算,先将初始值3和第一个分区的结果1传给combine函数,返回4,然后将4和第二个分区结果3传给combine函数,返回最终结果7。

简单说就是先分区中使用seq(都带入初始值),在不同分区 使用comb函数(也带入初始值)

aggregateByKey(zeroValue)(seqOp,combOp,[ numTasks ])

当调用(K,V)对的数据集时,返回(K,U)对的数据集,其中使用给定的组合函数和中性的“零”值来汇总每个键的值。允许与输入值类型不同的聚合值类型,同时避免不必要的分配。

import org.apache.spark.SparkConf  
import org.apache.spark.SparkContext  

object AggregateByKeyOp {  
  def main(args:Array[String]){  
     val sparkConf: SparkConf = new SparkConf().setAppName("AggregateByKey").setMaster("local")  
    val sc: SparkContext = new SparkContext(sparkConf)  

     val data=List((1,3),(1,2),(1,4),(2,3))  
     val rdd=sc.parallelize(data, 2)  

     //合并不同partition中的值,a,b得数据类型为zeroValue的数据类型  
     def combOp(a:String,b:String):String={  
       println("combOp: "+a+"\t"+b)  
       a+b  
     }  
     //合并在同一个partition中的值,a的数据类型为zeroValue的数据类型,b的数据类型为原value的数据类型  
      def seqOp(a:String,b:Int):String={  
        println("SeqOp:"+a+"\t"+b)  
        a+b  
      }  
      rdd.foreach(println)  
      //zeroValue:中立值,定义返回value的类型,并参与运算  
      //seqOp:用来在同一个partition中合并值  
      //combOp:用来在不同partiton中合并值  
      val aggregateByKeyRDD=rdd.aggregateByKey("100")(seqOp, combOp)  
      sc.stop()  
  }  
}

将数据拆分成两个分区

//分区一数据
(1,3)
(1,2)
//分区二数据
(1,4)
(2,3)

//分区一相同key的数据进行合并
seq: 100     3   //(1,3)开始和中立值进行合并  合并结果为 1003
seq: 1003     2   //(1,2)再次合并 结果为 10032

//分区二相同key的数据进行合并
seq: 100     4  //(1,4) 开始和中立值进行合并 1004
seq: 100     3  //(2,3) 开始和中立值进行合并 1003

将两个分区的结果进行合并
//key为2的,只在一个分区存在,不需要合并 (2,1003)
(2,1003)

//key为1的, 在两个分区存在,并且数据类型一致,合并
comb: 10032     1004
(1,100321004)
  def main(args: Array[String]) {

    System.setProperty("hadoop.home.dir", "D:\\hadoop-2.6.4\\hadoop-2.6.4")

    val conf = new SparkConf().setAppName("Join").setMaster("local")
    val sc = new SparkContext(conf)

    val data = sc.parallelize(List(1,2,3,4,5,6),2)
        def seq(a:Int, b:Int): Int = {
          println("seq: " + a + "\t " + b)
          math.min(a, b)
        }

        def comb(a:Int,b:Int):Int={
          println("comb : "+a+"\t"+b)
          a+b
       }

    println("====="+data.aggregate(3)(seq,comb))

  }

简单说就是先分区中不同的key使用seq(不同key都带入初始值),在不同分区的key 使用comb函数(不带初始值)

sortByKey([ascending], [numTasks])

当调用K实现Ordered的(K,V)对的数据集时,按照布尔ascending参数中的指定,按照升序或降序返回按键排序的(K,V)对的数据集。

val d2 = sc.parallelize(Array(("cc",32),("bb",32),("cc",22),("aa",18),("bb",6),("dd",16),("ee",104),("cc",1),("ff",13),("gg",68),("bb",44)))  
d2.sortByKey(false).collect  

输出:
res60: Array[(String, Int)] = Array((gg,68), (ff,13), (ee,104), (dd,16), (cc,32), (cc,22), (cc,1), (bb,32), (bb,6), (bb,44), (aa,18))

join(otherDataset, [numTasks])

当(K,V)和(K,W)类型的数据集被调用时,返回(K,(V,W))对的每个键的所有元素对的数据集。外连接通过支持leftOuterJoin,rightOuterJoin和fullOuterJoin。

val a = sc.parallelize(Array(("123",4.0),("456",9.0),("789",9.0)) 
val b = sc.parallelize(Array(("123",8.0),("789",10)))
val c = a.join(b)
c.foreach(println)
/*
(123,(4.0,8.0))
(789,(9.0,10))
 */

cogroup(otherDataset, [numTasks])

当(K,V)和(K,W)类型的数据集被调用时,返回(K,(Iterable ,Iterable ))元组的数据集。这个操作也被称为groupWith。

val a = sc.parallelize(Array(("123",4.0),("456",9.0),("789",9.0)) 
val b = sc.parallelize(Array(("123",8.0),("789",10)))
val d = a.cogroup(b)
d.foreach(println)
/*
(456,(CompactBuffer(9.0),CompactBuffer()))
(123,(CompactBuffer(4.0),CompactBuffer(8.0)))
(789,(CompactBuffer(9.0),CompactBuffer(10)))
*/

pipe(command, [envVars])

通过shell命令管理RDD的每个分区,例如Perl或bash脚本。RDD元素被写入进程的stdin,输出到stdout的行被作为字符串的RDD返回。

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext

object PipeTest {

  def main(args: Array[String]) {
    val sparkConf = new SparkConf().setAppName("pipe Test")
    val sc = new SparkContext(sparkConf)

    val data1 = Array[(String, Int)](
      ("K1", 1), ("K2", 2),
      ("U1", 3), ("U2", 4),
      ("W1", 3), ("W2", 4), )
    val pairs = sc.parallelize(data1, 3)
    val finalRDD = pairs.pipe("grep 2")
    finalRDD.foreach(x => println("!!!!!!!! " + x + " !!!!!!!!"))//输出
    //(K2,2)
    //(U2,4)
    //(W2,4)
    sc.stop()
  }

}

cartesian(otherDataset):

对两Rdd进笛卡尔积操作

val rdd1 = sc.parallelize(1 to 3)
val rdd2 = sc.parallelize(2 to 5)
val cartesianRDD = rdd1.cartesian(rdd2)
cartesianRDD.collect.foreach(println)
(1,2)
(1,3)
(1,4)
(1,5)
(2,2)
(2,3)
(2,4)
(2,5)
(3,2)
(3,3)
(3,4)
(3,5)

coalesce(numPartitions,shuffle):

对RDD的分区进行重新分区,shuffle默认值为false,当shuffle=false时,不能增加分区数
目,但不会报错,只是分区个数还是原来的

val rdd = sc.parallelize(1 to 16,4)
val coalesceRDD = rdd.coalesce(3) //当suffle的值为false时,不能增加分区数(即分区数不能从5->7)
println("重新分区后的分区个数:"+coalesceRDD.partitions.size) 
输出
重新分区后的分区个数:3

repartition(numPartition):

是函数coalesce(numPartition,true)的实现

glom():

将RDD的每个分区中的类型为T的元素转换换数组Array[T]

val rdd = sc.parallelize(1 to 16,4)
val glomRDD = rdd.glom() //RDD[Array[T]]
glomRDD.foreach(rdd => println(rdd.getClass.getSimpleName))

randomSplit(weight:Array[Double],seed):

val rdd = sc.parallelize(1 to 10)
val randomSplitRDD = rdd.randomSplit(Array(1.0,2.0,7.0))

scala> randomSplitRDD(0).foreach(x => print(x +" "))
为空
scala> randomSplitRDD(1).foreach(x => print(x +" "))
8
scala> randomSplitRDD(2).foreach(x => print(x +" "))
9 3 1 6 2 4 10 5 7

repartitionAndSortWithinPartitions(partitioner)

根据给定的分区器对RDD进行重新分区,并在每个结果分区中按键分类记录。这比repartition在每个分区中调用然后排序更高效,因为它可以将排序推送到洗牌机器中。

repartitionAndSortWithinPartitions,官方建议,如果需要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithinPartitions算子。因为该算子可以一边进行重分区的shuffle操作,一边进行排序。shuffle与sort两个操作同时进行,比先shuffle再sort来说,性能可能是要高的。

import org.apache.spark.{SparkContext, SparkConf}

/**
 * Created by sunxufeng on 2016/6/18.
 */
class Student {

}


//创建key类,key组合键为grade,score
case class StudentKey(grade:String,score:Int)
//  extends Ordered[StudentKey]{
//  def compare(that: StudentKey) : Int = {
//    var result:Int = this.grade.compareTo(that.grade)
//    if (result == 0){
//      result = this.student.compareTo(that.student)
//      if(result ==0){
//        result = that.score.compareTo(this.score)
//      }
//    }
//    result
//  }
//}

object StudentKey {
  implicit def orderingByGradeStudentScore[A <: StudentKey] : Ordering[A] = {
//    Ordering.by(fk => (fk.grade, fk.student, fk.score * -1))
    Ordering.by(fk => (fk.grade, fk.score * -1))
  }
}

object Student{
  def main(args: Array[String]) {



    //定义hdfs文件索引值
    val grade_idx:Int=0
    val student_idx:Int=1
    val course_idx:Int=2
    val score_idx:Int=3

    //定义转化函数,不能转化为Int类型的,给默认值0
    def safeInt(s: String): Int = try { s.toInt } catch { case _: Throwable  => 0 }

    //定义提取key的函数
    def createKey(data: Array[String]):StudentKey={
      StudentKey(data(grade_idx),safeInt(data(score_idx)))
    }

    //定义提取value的函数
    def listData(data: Array[String]):List[String]={
      List(data(grade_idx),data(student_idx),data(course_idx),data(score_idx))
    }

    def createKeyValueTuple(data: Array[String]) :(StudentKey,List[String]) = {
      (createKey(data),listData(data))
    }

    //创建分区类
    import org.apache.spark.Partitioner
    class StudentPartitioner(partitions: Int) extends Partitioner {
      require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative.")

      override def numPartitions: Int = partitions

      override def getPartition(key: Any): Int = {
        val k = key.asInstanceOf[StudentKey]
        k.grade.hashCode() % numPartitions
      }
    }

    //设置master为local,用来进行本地调试
    val conf = new SparkConf().setAppName("Student_partition_sort").setMaster("local")
    val sc = new SparkContext(conf)

    //学生信息是打乱的
    val student_array =Array(
      "c001,n003,chinese,59",
      "c002,n004,english,79",
      "c002,n004,chinese,13",
      "c001,n001,english,88",
      "c001,n002,chinese,10",
      "c002,n006,chinese,29",
      "c001,n001,chinese,54",
      "c001,n002,english,32",
      "c001,n003,english,43",
      "c002,n005,english,80",
      "c002,n005,chinese,48",
      "c002,n006,english,69"
      )
   //将学生信息并行化为rdd
    val student_rdd = sc.parallelize(student_array)
   //生成key-value格式的rdd
    val student_rdd2 = student_rdd.map(line => line.split(",")).map(createKeyValueTuple)
    //根据StudentKey中的grade进行分区,并根据score降序排列
    val student_rdd3 = student_rdd2.repartitionAndSortWithinPartitions(new StudentPartitioner(10))
   //打印数据
    student_rdd3.collect.foreach(println)
  }
}

引用:http://blog.csdn.net/jewes/article/details/39896301

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值