声明:
该文档根据spark工程师qq群(511947673)中提供的rdd-api.pdf文档中rdd顺序,进行了一系列的测试。
rdd-api.pdf下载地址:http://download.csdn.net/download/weinierzui/10158394
部分不详细的rdd/transform参考:
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*/
}
}