spark RDD

声明:

该文档根据spark工程师qq群(511947673)中提供的rdd-api.pdf文档中rdd顺序,进行了一系列的测试。

rdd-api.pdf下载地址:http://download.csdn.net/download/weinierzui/10158394

部分不详细的rdd/transform参考:

http://homepage.cs.latrobe.edu.au/zhe/ZhenHeSparkRDDAPIExamples.html。该文档很详细地归纳了rdd,也包含完全的例子。

建议: 学习过程中不懂的部分直接查看该rdd/transform的源码中的函数声明,真的是非常的简介明了的介绍。对理解功能有很大帮助。

完全测试代码如下:(如果有什么不对的地方,请留言告知,或者qq:1970497138)
import org.apache.hadoop.fs.Path
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.mapred.{FileOutputFormat, JobConf}
import org.apache.hadoop.mapreduce.Job
import org.apache.spark.partial.{BoundedDouble, PartialResult}
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
import org.apache.spark.storage.StorageLevel
import org.apache.spark.util.Utils

import scala.collection.Map
import scala.collection.mutable.ArrayBuffer

/**
  * 该类包含spark RDD.scala以及PairRDDFuncions.scala中,RDD的Transformation/action的介绍及例子
  *
  * 以及PairRDDFunctions中基础RDD API的介绍及例子。  启动部分RDD中的内部实现还是调用PairRDDFuntions中的方法/函数
  */
object RDDTest {

  def main(args: Array[String]): Unit = {

    System.setProperty("hadoop.home.dir","E:\\software\\hadoop-2.6.5")
    val sp = SparkSession.builder().master("local[2]").appName("RDDTest").getOrCreate()
    sp.sparkContext.setLogLevel("ERROR")

    //将数组通过并行化的形式转换为RDD(分布式弹性数据集)
    val rdd = sp.sparkContext.parallelize(Array(1,2,3,4,5,6,3,4,6,7,8,9,10))



    /*
      Transformations 部分
     */
    //将rdd结果缓存下来,以便后续使用时,直接拿取结果不需要再重复计算。(如果不缓存默认是重复计算之前的步骤)
    rdd.persist(StorageLevel.MEMORY_ONLY)

    //map,对RDD中的数据进行计算处理,之后返回一个新的RDD
   rdd.map(x=>{
      if(x%2==0) x
    })//.foreach(println(_))

    //keyBy  根据传入的函数(f*10) 计算得到结果,组成(f*10,f)的元组返回
    //如果想编写自动调用传入方法的方法,可以看看keyBy的源码。内部将f*10封装成一个闭包。
    val keyByRdd = rdd.keyBy(f=> f*10)
    //foreach中不可对返回的tuple直接用(a,b)接收。keyByRdd.foreach((a,b)=>println(a+"   "+b))
   // keyByRdd.foreach(x=>println(x._1+"   "+x._2))

    //flatMap返回值类型继承自Any,按理说应该可以返回任何类型的,类似x*10这样的操作无法执行。
    //flatMap分为两步执行,先执行map操作,再将map得到的结果汇聚到一个集合当中返回。
   val flatmaprdd =  rdd.flatMap(x=>List(x))
    //flatmaprdd.foreach(println(_))

    //filter内部函数的返回值必须是boolean类型,filter根据返回值确定是否返回该值。
    rdd.filter(x=>x%2==0)//.foreach(println(_))

    //distinct
    //去重操作,无参方法。
    rdd.distinct()//.foreach(println(_))
    //该步骤是distinct内部的实现逻辑。
    rdd.map(x=>(x,null)).reduceByKey((x,y)=>x,1).map(_._1)//.foreach(println(_))

      //重分区操作,用于RDD增减分区,内部实现也用的是coalesce,默认是开启shuffle操作的
    //得到分区数为10
    val numpartitions = rdd.repartition(10).getNumPartitions
    //println("numpartitions"+numpartitions)

    //特指减少分区,可以通过一次窄依赖的映射避免shuffle,默认关闭shuffle操作。
    //得到分区数为2  此处给出的分区数是最大分区数。实际产生的分区数可能比这个小。
    /** 源码中调用的 new CoalescedRDD(this, numPartitions, partitionCoalescer) 可看到下面定义为maxpartition。
      * private[spark] class CoalescedRDD[T: ClassTag](
      var prev: RDD[T],
       maxPartitions: Int,
      partitionCoalescer: Option[PartitionCoalescer] = None)
      extends RDD[T](prev.context, Nil)
      */
    val numcoalesce = rdd.coalesce(10).getNumPartitions
   // println("numcoalesce"+numcoalesce)

    //按占比随机抽样,false不覆盖,0.1为样本比例10%,3为随机因子(带默认参数,可不传)
    rdd.sample(false,0.1,3)//.foreach(println(_))

    //分组随机抽样,不太懂,根据输入的数组元素个数返回,相应数目的RDD组成的数组。作为抽样的分组结果
    rdd.randomSplit(Array(0.5,0.3))//.foreach(x=>{println(s"rdd output $x");x.foreach(println(_))})
    //随机抽样指定抽取的个数,false不覆盖,5为样本个数,3为随机因子(带默认参数,可不传)
    rdd.takeSample(false,5,3)//.foreach(println(_))

    //合并两个rdd,不去重.简单地说就是把两个rdd合并成1个rdd
    val rddkv = rdd.map(x=>(x+8,x+2))
    rdd.map(x=>(x,0)).union(rddkv)//.foreach(print(_))

    //传入的f是提取rdd中排序依据列的函数。true为是否升序,1为分区数,设置为1就是全局排序(有shuffle操作)。否则就是分区内排序。
    rdd.sortBy(x=>x,true,1)//.foreach(println(_))
    //sortBy内部的实现就是依据下面代码:sortByKey(是否升序,分区数)    keys用于获取rdd中元组的第一个元素组成的RDD,value同样的效果。
    rdd.keyBy(x=>x).sortByKey(true,1).keys//.foreach(println(_))

    //两个集合取交集,并去重。返回得到的交集组成的RDD
    //内部实现中cogroup返回,返回一个包含key和该key的values组成的list组成的元组(key,List(values)).用于后续计算。cogroup过程可能包含一次shuffle操作,为了两边RDD的分区对齐。
    rddkv.keys.intersection(rdd)//.foreach(println(_))


    //glom():RDD[Array[T]]把每个分区的数据合并成一个Array,原本每个区是[T]的迭代器
    //println(rdd.getNumPartitions)
    rdd.glom()
     /* .foreach(x=>{
      for(arr<-x){
        print("---"+arr)
      }
      println()
    })*/

    //求两个集合的笛卡尔积。RDD的做法事两个RDD,外循环yield出每对(x,y)
    rdd.cartesian(rddkv.keys)//.foreach(x=>println("("+x._1+","+x._2+")"))

    //groupby根据输入函数确定分组策略,groupBy[K](f: T => K):RDD[(K, Iterable[T])],返回结果包含同组内地数据的集合。
    //rdd建议如果后续跟agg的话,直接使用aggregateByKey或reduceBykey更省时,这两个操作本质就是combineByKey。
    rddkv.groupBy(x=>x._1)//.foreach(x=>println(x._1+"->>"+x._2))


    /*rddkv.foreach(println(_))
    (9,1)
    (11,3)
    (10,2)
    (12,4)
    (11,3)
    (14,6)
    (12,4)
    (15,7)
    (13,5)
    (16,8)
    (14,6)
    (17,9)
    (18,10)

    println("--------------------")*/
    //aggregate  将每个分区里面的元素进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致。
    // aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U = withScope
    //zeroValue类型和调用aggregate的类型一致,给与对分区数据聚合的初始值,此处rdd为tuple2故初始值为(0,0)
    //seqOp:(U,T)  第一个参数:第一次运算为初始值zeroValue,之后是上一次计算返回的结果组成的tuple
    //             第二个参数:rdd中的元素的类型,如果为rdd[Int],则该参数为Int类型,如果为rdd[(Int,Int)],则参数为(Int,Int)
    //combOp:(U, U) 两个参数表示分区数据合并之后得到的结果,combOp根据定义的函数体,将分区的数据按照指定规则进行处理。
    //注意:  根据函数定义可以出最终返回值的定义由zeroValue的类型决定,同样的zeroValue可以是符合运算的任何值。
    val tuple = rdd.aggregate((0,0))(
      (acc,ar)=>(ar+acc._1,ar+acc._2)
    ,(par1,part2)=>(par1._1+part2._1,par1._2+part2._2)
    )
    //println(tuple._1+"----"+tuple._2)
   //result  68----68

    //aggregateByKey  对PairRDD中相同的Key值进行聚合操作,在聚合过程中同样适用一个中立的初始值。
    //和aggregate函数相似,aggregateByKey返回值的类型不需要和RDD中的Value的类型一致。因为aggregateByKey是对相同Key中的值进行局和操作,所以aggregateByKey函数最终返回的类型还是PairRDD,
    //对应额结果是Key和聚合后的值,而aggregate函数直接返回的是非RDD的结果。
    val tuple2 = rddkv.aggregateByKey("100")(
      (acc,arr)=>(acc+arr),
      (par1,par2)=>(par1+par2)
    )
     /* .foreach(x=>{
      val rdd = x._2
      println("----"+x._1+"----"+rdd)
    })*/
   /* ----13----1005
    ----16----1008
    ----15----1007
    ----14----10061006
    ----11----10031003
    ----18----10010
    ----17----1009
    ----12----10041004
    ----9----1001
    ----10----1002*/

    //reduceByKey
    //该函数用于将RDD[K,V]中每个K对应的V值根据映射函数来运算。 并且带有去重功能。  调用 combineByKey
    rddkv.reduceByKey((x,y)=>x)//.foreach(println)
    /*(13,5)
    (16,8)
    (15,7)
    (14,6)
    (11,3)     --原本有两个,处理之后只有一个
    (18,10)
    (17,9)
    (12,4)
    (9,1)
    (10,2)*/

    //pipe
    //Return an RDD created by piping elements to a forked external process.
    //把rdd通过管道pipe发送到外部进程中,并返回一个新的rdd。   //下面例子来自网络:执行一个脚本。http://blog.csdn.net/guotong1988/article/details/50801151
    //测试通过可用。
    //val sparkConf = new SparkConf().setAppName("pipe Test")
    //val sc = new SparkContext(sparkConf)
    /*val sc = sp.sparkContext
    val data = List("hi", "hello", "how", "are", "you")
    val dataRDD = sc.makeRDD(data)//out123.txt里有hi hello how are you,如果加一个参数变成sc.makeRDD(data,2)则是how are you,我想应该是只有一个worker的缘故
    val scriptPath = "file:\\\\\\E:\\fromlinux\\echo.sh"       //此处路径读取的是本地路径
    val pipeRDD = dataRDD.pipe(scriptPath)
    print(pipeRDD.collect())
    sc.stop()*/
    //"/home/gt/spark/bin/echo.sh"
   /* #!/bin/bash
      echo "Running shell script";
    RESULT="";#变量两端不能直接接空格符
    while read LINE; do
      RESULT=${RESULT}" "${LINE}
    done
    echo ${RESULT} > out123.txt*/



    //mapPartitions[U: ClassTag](f: Iterator[T] => Iterator[U],preservesPartitioning: Boolean = false): RDD[U]
    //作用:将rdd的每个分区中的数据组成iterator的形式进行map操作,之后再以iterator的形式返回
    //U 根据RDD中的数据类型自动确定,f为对该分区的数据进行处理的函数,preservesPartitioning 默认为false,具体用途还不太清楚
    rdd.mapPartitions(x=>{
      val Arr = new ArrayBuffer[Int]()
      for(a<-x){
        Arr+= a*10
      }
      println("-----------------------------")
      Arr.iterator
    })//.foreach(println(_))
  /* 结果
   -----------------------------
    30
    40
    60
    70
    80
    90
    100
    -----------------------------
    10
    20
    30
    40
    50
    60*/


    //zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
    //将两个rdd进行拉链组合成元组的形式,每个rdd中获取的一次元素,作为合并元组中的一个元素。
   //(rdd.value,rddkv.value)---> (10,(18,12))
    rdd.zip(rddkv)//.foreach(println(_))


    /**
      * Action 部分
      */

    //foreach(f: T => Unit)
    //rdd 实现为调用 sc.runJob(), 把 f 作用于每个分区的每条记录
   // rdd.foreach(println(_))

    //foreachPartition(f: Iterator[T] => Unit)
    //rdd 实现为调用 sc.runJob(), 把 f 作用于每个分区
    /*rdd.foreachPartition(iters=>{
      for(iter<-iters){
        print(iter+"   ")
      }
      println("--------------------")
    })*/

    //collect(): Array[T]
    //rdd 实现为调用 sc.runJob(), 得到 results, 把多个 result 的 array 合并成一个 array
    //将数据集中到一个数组中。
    rdd.collect()//.foreach(print(_))

    //collect[U: ClassTag](f: PartialFunction[T, U]): RDD[U]
    //rdd 实现为 filter(f.isDefinedAt).map(f) 先做一次 filter 找出满足的数据, 然后一次 map 操作执行这个偏函数(PartialFunction)
    //定义偏函数,下面使用
    val one:PartialFunction[Int,String] = {case 2 =>"one";case _=>"other"}
    rdd.collect(one)//.foreach(println(_))

    //Return an iterator that contains all of the elements in this RDD.  返回一个包含所有元素的迭代器。
    //The iterator will consume as much memory as the largest partition in this RDD.  此迭代器消耗该rDD中分区的最大内存空间。
    //把所有数据以迭代器返回, rdd 实现是调用 sc.runJob(), 每个分区迭代器转 array, 收集到 driver 端再 flatMap 一次打散成大迭代器。 理解为一种比较特殊的 driver 端 cache
    rdd.toLocalIterator//.foreach(println(_))


    //Return an RDD with the elements from `this` that are not in `other`.   返回在该rdd中存在,但是在参数rddkv中不存在的元素组成的rdd
    // subtract(other: RDD[T]): RDD[T]
    /*rddkv.values.collect().foreach(println)
    println("=====rddkv=====")
    rdd.collect().foreach(println)
    println("=====rdd=====")*/
    rdd.subtract(rddkv.values)//.foreach(println(_))


    //将rdd中的元素,进行类似自身元素求和的操作,结果返回一个操作结果的元组。
    //reduce(f: (T, T) => T): T
    //rdd 实现为调用 sc.runJob(), 让 f 在 rdd 每个分区计算一次, 最后汇总 merge 的时候再计算一次
    val tuple3 = rddkv.reduce((x,y)=>{
      (x._1+y._1,x._2+y._2)
    })
    //println(tuple3)

    //Reduces the elements of this RDD in a multi-level tree pattern  以多级树形模式减少此RDD的元素
    //treeReduce(f: (T, T) => T, depth: Int = 2)     depth  设置树的深度,默认为2,没搞明白depth是干嘛的
    //与treeAggregate一样
    val tuple22 = rddkv.zip(rdd).treeReduce((x,y)=>{
      ((x._1._1+y._1._1,x._1._2+y._1._2),x._2+y._2)
    },2)
    //println(tuple22)

    //fold(zeroValue: T)(op: (T, T) => T): T        trans:合拢,折叠
    //特殊的reduce,带初始值。
    //注意:初始值在每个分区计算的时候,都会加上。在最后汇总的时候还会再加一次该初始值。导致结果多加了分区个数次初始值。
    val res = rdd.fold(100)((x,y)=>{
      //println(x+"----"+y)
      x+y
    })
    //println(res)


    //aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
    //seqOp  an operator used to accumulate results within a partition   作用于每个分区的函数
    //combOp an associative operator used to combine results from different partitions  对不同分区进行后续聚合的函数
    //与上面Transformation中的aggregate用法一致
    //包含的初始值,与上面fold方法一致,每个分区和最后的汇总处理都会把该初始值作用上,最终导致多加了分区数此的初始值。

  val resa =   rdd.aggregate(100)(
      (res,rddv)=>{
       res+rddv
    },
      (one,two)=>{
        one+two
      }
    )
    //println(resa)

    //作用于上面的treeReduce一致
    //Aggregates the elements of this RDD in a multi-level tree pattern.
    //treeAggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U,combOp: (U, U) => U,depth: Int = 2): U
    //此action 对初始值的使用次数是分区数个数次。与上面aggregate相比少一次各分区汇总时的默认值使用
    //在分区处, 做两次及以上的 merge 聚合, 即每个分区的 merge 计算可能也会带 shuffle。 其余部分同aggregate。 理解为更复杂的多阶 aggregate
  val resta =  rdd.treeAggregate(100)(
     (res,rddv)=>(res+rddv),
     (one,two)=>(one+two),2
   )
    //println(resta)

    //countApprox(timeout: Long,confidence: Double = 0.95): PartialResult[BoundedDouble]
    //timeout 单位milliseconds 设置超时时间,confidence设置数据准确度。默认是0.95
    //近似版本的count,在超时时间范围内,返回一个不完整的结果值,即使不是所有任务都完成了。
   //如果在有效时间内计算完成返回:(final: [13.000, 13.000])
    //                 未完成返回:(partial: [0.000, Infinity])
    //可以根据返回值的状态判断是否计算完成。
    //提交个体 DAGScheduler 特殊的任务, 生成特殊的任务监听者, 在 timeout 时间内返回, 没计算完的话返回一个大致结果, 返回值的计算逻辑可见 ApproximateEvaluator 的子类
    //countByValueApprox() 同 countApprox()
    //countByValueApprox() is Approximate version of countByValue().
   val resc =  rdd.countApprox(2000,1)
   // println(resc)

    //rdd 实现为 map(value => (value, null)).countByKey() 本质上是一次简单的 combineByKey, 返回 Map,会全 load 进 driver 的内存里, 需要数据集规模较小
    //功能:返回类型为map,内容是rdd中元素及出现的次数(rddvalue,num)
    val resmap = rddkv.countByValue()
     // println(resmap)


    //返回近似值:对rdd distinct操作结果的元素个数的近似值
    val numApp = rdd.countApproxDistinct()
   // println(numApp)

    //zipWithIndex  将RDD的元素与元素索引相齐。索引从0开始值为连续的,最后一个索引值组成的结构为索引最大值。如果RDD分散在多个分区上,那么将启动一个spark作业来执行此操作。
    rdd.zipWithIndex()//.collect().foreach(println)


    //zipWithUniqueId
    //这与zipWithIndex不同,因为它只给每个数据元素提供一个惟一的id,但是id可能与数据元素的索引号不匹配。即使RDD分散在多个分区上,这个操作也不会启动spark任务
    rdd.zipWithUniqueId()//.collect().foreach(println)

    //提取RDD的前n项,并将它们作为数组返回。(注意:这听起来很简单,但对于Spark的实现者来说,这实际上是一个相当棘手的问题,因为涉及的项目可以在许多不同的分区中。)
    rdd.take(10)//.foreach(println)

    //等同于rdd.take(1)
    rdd.first()

    //每个分区内传入 top 的处理函数, 得到分区的堆,使用 rdd.reduce(), 把每个分区的堆合起来, 排序, 取前 n 个
    rdd.top(10)//.foreach(println)

    //特殊的reduce,传入max/min比较函数。(查看源码)
    //返回最大值,最小值。
    rdd.max()
    rdd.min()

    //保存文件到文件系统,此处需要读取hadoop环境配置,如果没有配置hadoop安装目录,很可能目录创建成功了但是会报空指针。
    //rdd.saveAsObjectFile("")
    if(!rdd.isEmpty()){
      rdd.repartition(1)//.saveAsTextFile("file:\\E:\\fromlinux\\output\\rdd")
    }

  //checkpoint
    //将在下一次计算RDD时创建一个检查点。检查点的RDDs作为二进制文件存储在检查点目录中,可以使用Spark上下文指定。(警告:Spark应用惰性评估。在调用action操作之前,将不会出现检查点。)
    //重要提示:“mydirectoryname”的目录应该存在于所有的节点中。作为另一种选择,您也可以使用HDFS目录URL。hdfs://HADOOP8:8088/user/zlhao/my_directory_name
    //检查点数据存放目录需要提前设置。
    sp.sparkContext.setCheckpointDir("my_directory_name")
    rdd.checkpoint()


    /**
      * 特殊 RDD
      *
      * 很多上面RDD中的操作内部都是调用的PairRDDFunctions中的方法实现的。
      * PairRDDFunctions
      */


    //combineBykey      RDD中相当一部分操作都调用了该方法。
    //def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C): RDD[(K, C)]
    //def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C, numPartitions: Int): RDD[(K, C)]
    //def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C, partitioner: Partitioner, mapSideCombine: Boolean = true, serializerClass: String = null): RDD[(K, C)]
    //作用:根据rdd中的key,进行分组统计,将key相同的value合并到一个集合中,并最后将各分区的结果进行合并产生(k,iterator(values))的形式
    //参数介绍:createCombiner  获取rdd中value的元素,并构造成C类型。
    //         mergeValue  输入参数为createCombiner的结果值,V为rdd中其他的value值,进行根据key分组汇总。  函数体部分只提供元素合并的方式。
    //         mergeCombiners 合并不同分区中的返回结果,类似类似reduce过程。传入参数是分区的返回结果。
    //可以指定结果的分区数,以及是否开启map段合并,以减少shuffle操作。
    val rddi = sp.sparkContext.parallelize(List(1,1,2,2,2,1,2,2,2),3)
    val rdda = sp.sparkContext.parallelize(List("dog","cat","gnu","salmon","rabbit","turkey","wolf","bear","bee"), 3)
    val rddc = rddi.zip(rdda)
    val rddd = rddc.combineByKey(List(_),(x:List[String],y:String)=>y::x, (x:List[String],y:List[String])=>x:::y)
    val rddtest = rddc.combineByKey(ArrayBuffer(_),(x:ArrayBuffer[String],y:String)=>x+=y,(x:ArrayBuffer[String],y:ArrayBuffer[String])=>x++=y)
     rddtest.collect()//.foreach(println)

    //func 即被当作 mergeValue, 又被当作 mergeCombiners, 调用了 combineByKey()
    //根据key对value值进行按照柯里化给定的函数体进行组合,得到(key,-----value---value)形式的rdd
    //柯里化函数可以省略。
    rddc.foldByKey("")((x:String,y:String)=>x+"----"+y).collect()//.foreach(println)

    //sampleByKey
    //sampleByKey(withReplacement: Boolean,fractions: Map[K, Double],seed: Long = Utils.random.nextLong): RDD[(K, V)]
    //根据您希望在最终的RDD中出现的每个键的分数,随机地对关键值对RDD进行采样。  seed为随机因子
    val randRDD = sp.sparkContext.parallelize(List( (7,"cat"), (6, "mouse"),(7, "cup"), (6, "book"), (7, "tv"), (6, "screen"), (7, "heater")))
    //此处设置randRDD中每个key测侧重比
    val sampleMap = List((7, 0.4), (6, 0.6)).toMap
    randRDD.sampleByKey(false, sampleMap,42).collect

    //与reduceBykey得到的结果一致。
    //内部实现 self.mapPartitions(reducePartition).reduce(mergeMaps) reducePartition 是在每个分区生成一个 HashMap, mergeMaps是合并多个 HashMap
    //该reduceBykey中传入的函数用于在分区之间合并的时候使用。(详见源码)
    rddc.reduceByKeyLocally((x,y)=>x+"><"+y)//.foreach(println)

    //countByKey操作根据key值,进行聚合得到的结果是(key值,key出现的次数)(key,keynum)
    rddc.countByKey()//.foreach(println)

    //countByKeyApprox [Pair]
    //推测该rdd功能是,根据key进行聚合,但是并非返回准确值,是一个在timeout时间范围内的预估值。参考countByValueApprox
    //Marked as experimental feature! Experimental features are currently not covered by this document!

    //
    //def groupByKey(): RDD[(K, Iterable[V])]
   // def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
   // def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
    //(1)
    rddc.groupByKey()//.foreach(println)
    //重分区对结果输出不影响。
   val parnum =  rddc.groupByKey(2).getNumPartitions
    //(2)
    rddc.groupByKey(2)//.foreach(println)
    //(3)   此处的两个函数getPartition控制分区(不太会用,可以产生hash值返回),numPartition设置分区数
    //该操作要保证所有的数据都能搞放到内存中,否则会报OutOfMemoryError
    rddc.groupByKey(new Partitioner {
      override def getPartition(key: Any): Int = key.hashCode()
      override def numPartitions: Int = 2
    })//.foreach(println)

    //partitionBy(partitioner: Partitioner): RDD[(K, V)]
    //为rdd设置重新的分组结构    getPartition设置分组策略,numPartitions设置分区数
    //此处如果分区数不合理可能导致数据倾斜,有的分区为空。
    //一般分组策略有根据key的hash值,以及随机分组。或者根据key的部分内容
    val resparnum = rddc.partitionBy(new Partitioner {
      override def getPartition(key: Any): Int =Math.random().toInt
      override def numPartitions: Int = 3
    })//.getNumPartitions
    //println(resparnum)
   /* resparnum.foreachPartition(x=>{
      for(v<-x){
        println(v+"----")
      }
      println("----------------------")
    })*/

    //join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
    //返回两个RDD根据key聚合join之后的交集。跟sql中的join效果一样
    val newrdd = rddkv.map(x=>(x._1-3,x._2))
    randRDD.join(newrdd)//.foreach(println)
   /* (6,(mouse,3))
    (7,(cat,4))
    (6,(book,3))
    (7,(cup,4))
    (6,(screen,3))
    (7,(tv,4))
    (7,(heater,4))*/

    //leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]
    //跟sql中的效果一样
    randRDD.leftOuterJoin(newrdd)//.foreach(println)
    /*(6,(mouse,Some(3)))
    (7,(cat,Some(4)))
    (6,(book,Some(3)))
    (7,(cup,Some(4)))
    (6,(screen,Some(3)))
    (7,(tv,Some(4)))
    (7,(heater,Some(4)))*/

    //rightOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (Option[V], W))]
    randRDD.rightOuterJoin(newrdd)//.foreach(println)
    /*(13,(None,10))
    (15,(None,12))
    (11,(None,8))
    (11,(None,8))
    (7,(Some(cat),4))
    (7,(Some(cup),4))
    (7,(Some(tv),4))
    (7,(Some(heater),4))
    (9,(None,6))
    (9,(None,6))
    (14,(None,11))
    (6,(Some(mouse),3))
    (6,(Some(book),3))
    (6,(Some(screen),3))
    (8,(None,5))
    (8,(None,5))
    (12,(None,9))
    (10,(None,7))*/

    //y与sql中的效果一样
    //fullOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (Option[V], Option[W]))]
    randRDD.fullOuterJoin(newrdd)//.foreach(println)
    /*(13,(None,Some(10)))
    (14,(None,Some(11)))
    (6,(Some(mouse),Some(3)))
    (6,(Some(book),Some(3)))
    (6,(Some(screen),Some(3)))
    (8,(None,Some(5)))
    (8,(None,Some(5)))
    (12,(None,Some(9)))
    (10,(None,Some(7)))
    (15,(None,Some(12)))
    (11,(None,Some(8)))
    (11,(None,Some(8)))
    (7,(Some(cat),Some(4)))
    (7,(Some(cup),Some(4)))
    (7,(Some(tv),Some(4)))
    (7,(Some(heater),Some(4)))
    (9,(None,Some(6)))
    (9,(None,Some(6)))*/

    //将RDD中的内容以map的形式返回,如果有多个相同的key,则只返回一个。
    //此方法用于小数据量的rdd,因为数据全部加载到driver的内存中
    //collectAsMap(): Map[K, V]
    val resmapt = randRDD.collectAsMap()
   // println(resmapt)

    //以键值对的形式返回rdd中的内容,可以自定义对value的值进行处理。
    randRDD.mapValues(x=>"--"+x)//.foreach(println)

    //flatMapValues[U](f: V => TraversableOnce[U]): RDD[(K, U)]
    //TraversableOnce 解释:A template trait for collections which can be traversed either once only or one or more times.
    //集合的模板,可以一个或一次或多次遍历
    //作用:对value值进行按照传入的函数规则进行处理,生成一个迭代器,用于后续产生新的rdd.  一次操作产生的多个结果,最终组成的元组的key,还是未拆分之前的key
    randRDD.flatMapValues(x=>{
      x.split("e").iterator    //此处如果是string字符串自动转换成一个char类型的iterator
    })//.foreach(print)
    //(7,cat)(6,mous)(7,cup)(6,book)(7,tv)(6,scr)(6,)(6,n)(7,h)(7,at)(7,r)

    //一个功能强大的函数,允许最多3个rdd根据key值汇聚成一个RDD,还可以对返回的结果进行重分区
//    def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
//    def cogroup[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))]
//    def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (Iterable[V], Iterable[W]))]
//    def cogroup[W1, W2](other1: RDD[(K, W1)], other2: RDD[(K, W2)]): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2]))]
//    def cogroup[W1, W2](other1: RDD[(K, W1)], other2: RDD[(K, W2)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2]))]
//    def cogroup[W1, W2](other1: RDD[(K, W1)], other2: RDD[(K, W2)], partitioner: Partitioner): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2]))]

    //groupWith有类似的功能
   /* def groupWith[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
    def groupWith[W1, W2](other1: RDD[(K, W1)], other2: RDD[(K, W2)]): RDD[(K, (Iterable[V], IterableW1], Iterable[W2]))]*/
    randRDD.cogroup(newrdd)//.foreach(println)  //输出结果同下
    randRDD.groupWith(newrdd)//.foreach(println)  //输出结果同下
    /*(14,(CompactBuffer(),CompactBuffer(11)))
    (6,(CompactBuffer(mouse, book, screen),CompactBuffer(3)))
    (8,(CompactBuffer(),CompactBuffer(5, 5)))
    (12,(CompactBuffer(),CompactBuffer(9)))
    (10,(CompactBuffer(),CompactBuffer(7)))
    (13,(CompactBuffer(),CompactBuffer(10)))
    (15,(CompactBuffer(),CompactBuffer(12)))
    (11,(CompactBuffer(),CompactBuffer(8, 8)))
    (7,(CompactBuffer(cat, cup, tv, heater),CompactBuffer(4)))
    (9,(CompactBuffer(),CompactBuffer(6, 6)))*/

    //subtractByKey[W: ClassTag](other: RDD[(K, W)]): RDD[(K, V)]
    //Return an RDD with the pairs from `this` whose keys are not in `other`.  返回一个rdd,其中的key在newrdd中不在randRDD中
    newrdd.subtractByKey(randRDD)//.foreach(println)

    //lookup(key: K): Seq[V]
    //在rdd中查找指定的key值,组成一个seq并返回。
    val resiter = randRDD.lookup(7)



    //saveAsXXX
    //Saves the RDD in a Hadoop compatible format using any Hadoop outputFormat class the user specifies.
    //将rdd根据用户指定的hadoop outputformat保存为Hadoop兼容的格式。
//    def saveAsHadoopDataset(conf: JobConf)
//    def saveAsHadoopFile[F <: OutputFormat[K, V]](path: String)(implicit fm: ClassTag[F])
//    def saveAsHadoopFile[F <: OutputFormat[K, V]](path: String, codec: Class[_ <: CompressionCodec]) (implicit fm: ClassTag[F])
//    def saveAsHadoopFile(path: String, keyClass: Class[_], valueClass: Class[_], outputFormatClass: Class[_ <: OutputFormat[_, _]], codec: Class[_ <: CompressionCodec])
//    def saveAsHadoopFile(path: String, keyClass: Class[_], valueClass: Class[_], outputFormatClass: Class[_ <: OutputFormat[_, _]], conf: JobConf = new JobConf(self.context.hadoopConfiguration), codec: Option[Class[_ <: CompressionCodec]] = None)
//    def saveAsNewAPIHadoopFile[F <: NewOutputFormat[K, V]](path: String)(implicit fm: ClassTag[F])
//    def saveAsNewAPIHadoopFile(path: String, keyClass: Class[_], valueClass: Class[_], outputFormatClass: Class[_ <: NewOutputFormat[_, _]], conf: Configuration = self.context.hadoopConfiguration)
    //JobConf  A map/reduce job configuration. 一个map/reduce任务的配置类
    //可以参考编写mapreduce程序时的设置项。
    /*我们讨论JobConf,其有很多的项可以进行配置:

    setInputFormat:设置map的输入格式,默认为TextInputFormat,key为LongWritable,value为Text
    setNumMapTasks:设置map任务的个数,此设置通常不起作用,map任务的个数取决于输入的数据所能分成的inputsplit的个数
    setMapperClass:设置Mapper,默认为IdentityMapper
    setMapRunnerClass:设置MapRunner, maptask是由MapRunner运行的,默认为MapRunnable,其功能为读取inputsplit的一个个record,依次调用Mapper的map函数
    setMapOutputKeyClass和setMapOutputValueClass:设置Mapper的输出的key-value对的格式
    setOutputKeyClass和setOutputValueClass:设置Reducer的输出的key-value对的格式
    setPartitionerClass和setNumReduceTasks:设置Partitioner,默认为HashPartitioner,其根据key的hash值来决定进入哪个partition,每个partition被一个reduce task处理,所以partition的个数等于reducetask的个数
    setReducerClass:设置Reducer,默认为IdentityReducer
    setOutputFormat:设置任务的输出格式,默认为TextOutputFormat
    FileInputFormat.addInputPath:设置输入文件的路径,可以使一个文件,一个路径,一个通配符。可以被调用多次添加多个路径
    FileOutputFormat.setOutputPath:设置输出文件的路径,在job运行前此路径不应该存在
    当然不用所有的都设置,由上面的例子,可以编写Map-Reduce程序如下:

    public class MaxTemperature {

      publicstatic void main(String[] args) throws IOException {

        if (args.length != 2) {

          System.err.println("Usage: MaxTemperature  ");

          System.exit(-1);

        }

        JobConf conf = new JobConf(MaxTemperature.class);

        conf.setJobName("Max temperature");

        FileInputFormat.addInputPath(conf, new Path(args[0]));

        FileOutputFormat.setOutputPath(conf, new Path(args[1]));

        conf.setMapperClass(MaxTemperatureMapper.class);

        conf.setReducerClass(MaxTemperatureReducer.class);

        conf.setOutputKeyClass(Text.class);

        conf.setOutputValueClass(IntWritable.class);

        JobClient.runJob(conf);

      }

    }*/
    val jobconf = new JobConf()
    //设置文件输出路径,在编写mapreduce程序时用到了,这样的设置。
    //设置的是全局的文件输出路径
    jobconf.setJobName("mapreduceOutput2Dir")
    //FileInputFormat.addInputPath(conf, new Nothing(args(0)))
    FileOutputFormat.setOutputPath(jobconf,new Path("file:\\E:\\fromlinux\\output\\test"))
   // randRDD.saveAsHadoopDataset(jobconf)

   //saveAsObjectFile   将rdd保存为一个二进制格式的文件,输入参数是路径
   // saveAsObjectFile(path: String)
   /* randRDD.saveAsObjectFile("testObj")
    val resobj = sp.sparkContext.objectFile("testObj")
    resobj.foreach(println)
*/
    //saveAsSequenceFile
    //sequenceFile读取的时候需要指定[k,v]的类型。[Null,org.apache.hadoop.io.BytesWritable] 否则会报错 :未找到隐式转换值之类的。
    /*randRDD.saveAsSequenceFile("seqFile")
    val seqfile = sp.sparkContext.sequenceFile[Null,org.apache.hadoop.io.BytesWritable]("seqFile")
    seqfile.foreach(println)*/

    //保存为textFile  saveAsTextFile
   /* rdd.saveAsTextFile("file:\\E:\\fromlinux\\output\\textFile")
    val textFile = sp.sparkContext.textFile("file:\\E:\\fromlinux\\output\\textFile")
    textFile.foreach(println)*/

    //获取rdd中key/value组成的集合,返回为RDD
    rddkv.keys
    rddkv.values



    /*
    AsyncRDDActions  异步的RDDaction

    //此部分没有例子,后续补充
     */
    //countAsync, collectAsync, takeAsync, foreachAsync, foreachPartitionAsync


    /*
    OrderedRDDFunctions  已排序的RDD函数
     */

    //sortByKey
    rddkv.repartition(1).sortByKey()//.foreach(println)

    //filterByRange(lower: K, upper: K): RDD[P]
    //返回[lower,upper]范围内的key的值。  此函数针对rdd采用RangePartition分区的时候才可以使用。
    rddkv.filterByRange(1,10)//.foreach(println)

    /*
    DoubleRDDFunctions
     */
    val ressum = rdd.sum()
   // println(ressum)

    //stats
    //rdd 实现是 mapPartitions(nums => Iterator(StatCounter(nums))).reduce((a, b) => a.merge(b))StatCounter 在一次遍历里统计出中位数、 方差、 count 三个值, merge()是他内部的方法
    //返回值包含:元素个数(count),平均值(mean),标准差(stdev),最大值(max),最小值(min)
    rdd.stats()

    //平均值
    //内部调用stats.mean
    rdd.mean()
    //方差
    //内部调用stats.variance
    rdd.variance()
    //标准差
    //内部调用stats.stdev
    rdd.stdev()

    //meanApprox
    //在超时时间内返回平均值。
    //(final: [5.231, 5.231])计算完成
   val resme = rdd.meanApprox(2000,0.95)
   println(resme)

    //histogram直方图
    /*
      使用bucketCount桶数平均计算数据的直方图在RDD的最小值和最大值之间隔开。
      例如,如果分钟值是0,最大值是100,最后有两个桶,水桶将[0,50)[50,100]。
      bucketCount必须至少为1
      如果RDD包含无穷大,则NaN将引发异常
      如果RDD中的元素不变(max == min),则始终返回一个桶。

      输入参数:表示需要分多少个范围
      返回值:第一个数组表示是数据分布在柱状图的范围[0,50)[50,100]。
             第二个数组表示,分布在每个范围内的数值的个数。
     */
    //rdd内容 Array(1,2,3,4,5,6,3,4,6,7,8,9,10)
    val reshis = rdd.histogram(2)
    reshis._1.foreach(println)
    println("=========")
    reshis._2.foreach(println)
    //上面表示两个柱状图的数据范围[1.0,5,5),[5.5,10]
    //下面表示该范围内的数值的个数
   /* 1.0
    5.5
    10.0
    =========
    7
    6*/
  }
}


你可能感兴趣的:(spark)