1. SparkRDD的操作
Spark RDD的操作,便是对RDD的转换执行操作。
从上图我们可以总结出,sparkRDD的操作,从宏观上分为:Transformation和Action,但是具体的还以分为
输入算子、变换算子、缓存算子,以及行动算子。
1.2. Transformation
1.2.1. flatMap
是Spark RDD中的转换算子,对RDD中的每一个元素都执行,前后元素的对应关系是one-2-many。也就是说,对一个元素执行RDD的操作,可以产生多个元素。
/**
* 主要作用就是做拆分
* one-2-many
* 将字符串进行单词的拆分
*/
def flatMapOps: Unit = {
val conf = new SparkConf()
.setAppName("SparkTransformation")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val list = List(
"yan xin xin",
"zhai zhao pin"
)
val listRDD = sc.parallelize(list)
val wordsRDD = listRDD.flatMap(line => line.split("\\s+"))
wordsRDD.foreach(println)
sc.stop()
}
1.2.2. map
是Spark RDD中的转换算子,对RDD中的每一个元素都执行,前后元素的对应关系是one-2-one。也就是说,对一个元素执行RDD的操作,可以产生一个元素。
/**
* 将集合中的每一个元素*7
*/
def mapOps(sc: SparkContext): Unit = {
val list = 1 to 7
val listRDD = sc.parallelize(list)
val retRDD = listRDD.map(_ * 7)
retRDD.foreach(println)
}
1.2.3. filter
对RDD中的每一个元素执行对应的func函数,保留该func函数返回值为true的元素,组成一个新的RDD,过滤掉返回值为false的元素。
/*
过滤掉班级中的男生,只留下小姐姐
stu
id name age gender
*/
def filterOps(sc: SparkContext): Unit = {
val stuList = List(
"1 候雪杰 18 0",
"2 扈紫豪 28 1",
"3 张钟方 16 0",
"4 白红卫 23 1"
)
val stuRDD:RDD[String] = sc.parallelize(stuList)
val girls = stuRDD.filter(stu => {
// val fields = stu.split("\\s+")
// val gender = fields(3)
// gender == "0"
stu.endsWith("0")
})
girls.foreach(println)
}
1.2.4. sample
这是Spark中的抽样算子,从RDD中抽取一定比例的数据,接收三个参数:
withReplacement: Boolean, true有放回的抽样, false无放回的抽样
fraction: Double,样本空间大小,占总大小的比例的分数的表现形式,20%–>0.2
seed: Long ,抽样过程中的随机数种子。
注意:该抽样算子sample是非准确式的抽样,加入rdd中有1000个记录,抽取0.2的数据,按理结果应该有200个,真实结果可能会在200上下左右浮动。
作用就是,对整体空间做一个大概的预估,key/数据的分布情况,在解决数据倾斜的时候有着非常重要的角色。
def sampleOps(sc:SparkContext): Unit = {
val list = 1 to 10000
val listRDD = sc.parallelize(list)
val sampledRDD = listRDD.sample(false, 0.2)
println("抽取样本空间的大小:" + sampledRDD.count())
}
1.2.5. union
在spark中的union操作,作用和DB中的union all的作用相同,不会去重。
union:返回两张表中去重之后的结果
union all:返回两张表的所有结果。
//作用类似于union all
def unionOps(sc: SparkContext): Unit = {
val listRDD1 = sc.parallelize(1 to 5)
val listRDD2 = sc.parallelize(3 to 10)
val unionRDD = listRDD1.union(listRDD2)
unionRDD.foreach(println)
}
1.2.6. join
表的关联操作,这里的表示泛化的概念,只要是数据集都可以理解为表。
join有哪些操作:
交叉连接:across join 写sql的时候有表的关联,但是没有on的连接字段,这会造成笛卡尔积。
select a.* , b.* from A a across join B b
内连接: inner(通常可省略) join,又被称为等值连接,返回左右表中都有的内容。
select a.* , b.* from A a inner join B b on a.id = b.aid;
select a.* , b.* from A a, B b where a.id = b.aid;
外连接 outer join 非等值连接
左外连接:left outer join 返回左表所有数据,右表没有显示为null
select a.* , b.* from A a left outer join B b on a.id = b.aid;
右外连接: right outer join返回右表所有数据,左表没有显示为null
select a.* , b.* from A a right outer join B b on a.id = b.aid;
半连接 semi join
全连接 full outer join 全连接: left outer join + right outer join
说明:经过内连接join操作之后的数据是确定的。外连接操作之的数据是确定的吗?不确定,有可能有,有可能没有-----Option。
/**
* stu表
* id name age class
* 成绩表
* id sid course score
*
* 查询有成绩的学生的所有的信息-->inner
* select
s.*, s1.*
from stu s
inner join score s1 on s.id = s1.sid
* 查询所有学生的成绩
* select
* s.*, s1.*
* from stu s
* left join s1 on on s.id = s1.sid
* 所有的join操作,必须要求的RDD的类型时<K, V>
* K就是关联字段
*/
def joinOps(sc: SparkContext): Unit = {
val stu = List(
"1 刘梦男 22 bd-1901-bj",
"2 常国龙 25 bd-1901-bj",
"3 张湟熹 24 bd-1901-sz",
"4 胡盼盼(男) 18 bd-1901-wh"
)
val scores = List(
"1 1 math 82",
"2 1 english 0",
"3 2 chinese 85.5",
"4 3 PE 99",
"5 10 math 99"
)
val stuRDD:RDD[String] = sc.parallelize(stu)
val scoreRDD:RDD[String] = sc.parallelize(scores)
//查询所有的学生信息
val sid2StuInfoRDD:RDD[(String, String)] = stuRDD.map(stuLine => {
val sid = stuLine.substring(0, 1)
val stuInfo = stuLine.substring(1).trim
(sid, stuInfo)
})
val sid2ScoreInfo:RDD[(String, String)] = scoreRDD.map(scoreLine => {
val fields = scoreLine.split("\\s+")
val sid = fields(1)
val scoreInfo = fields(2) + " " + fields(3)
(sid, scoreInfo)
})
//(K, V) join (K, W) ---> (K, (V, W))
val joinedRDD:RDD[(String, (String, String))] = sid2StuInfoRDD.join(sid2ScoreInfo)
/*joinedRDD.foreach(t => {
println(s"sid:${t._1}\tstuInfo:${t._2._1}\tscoreInfo:${t._2._2}")
})*/
joinedRDD.foreach{case (sid, (stuInfo, scoreInfo)) => {
println(s"sid:${sid}\tstuInfo:${stuInfo}\tscoreInfo:${scoreInfo}")
}}
println("------------------------------")
val leftJoined:RDD[(String, (String, Option[String]))] = sid2StuInfoRDD.leftOuterJoin(sid2ScoreInfo)
leftJoined.foreach{case (sid, (stuInfo, option)) => {
println(s"sid:${sid}\tstuInfo:${stuInfo}\tscoreInfo:${option.getOrElse(null)}")
}}
println("--------------full join----------------")
val fullJoinedRDD:RDD[(String, (Option[String], Option[String]))] = sid2StuInfoRDD.fullOuterJoin(sid2ScoreInfo)
fullJoinedRDD.foreach{case (sid, (stuOption, scoreOption)) => {
println(s"sid:${sid}\tstuInfo:${stuOption.getOrElse(null)}\tscoreInfo:${scoreOption.getOrElse(null)}")
}}
}
1.2.7. groupByKey
对rdd中的数据按照key进行分组,首先必须要有key,rdd的数据类型就必须是(K, V),经过分组之后相同的key的数据拉取到一起,组成了一个集合。
(K, V).groupByKey() --> (K, Iterable[V])
不建议使用这个groupByKey,可以使用reduceByKey或者aggregateByKey来代替gbk。因为性能差,没有本地shuffle==combiner。
def gbkOps(sc: SparkContext): Unit = {
val stu = List(
"1 刘梦男 22 bd-1901-bj",
"2 常国龙 25 bd-1901-bj",
"3 张湟熹 24 bd-1901-sz",
"4 胡盼盼(男) 18 bd-1901-wh",
"5 周联动 18 bd-1901-wh",
"4 张震 18 bd-1901-sz"
)
//按照班级进行分组 -->sql中的groupBy
val stuRDD:RDD[String] = sc.parallelize(stu)
val class2Info:RDD[(String, String)] = stuRDD.map(stuLine => {
val clazz = stuLine.substring(stuLine.indexOf("bd"))
val info = stuLine.substring(0, stuLine.indexOf("bd")).trim
(clazz, info)
})
val gbkRDD:RDD[(String, Iterable[String])] = class2Info.groupByKey()
gbkRDD.foreach{case (clazz, infos) => {
/*for(info <- infos) {
println(s"${clazz} ---> ${info}")
}*/
println(s"${clazz} ---> ${infos}")
}}
println("-----------------------------------------")
//ClassTag是类型的标记接口
val gbRDD:RDD[(String, Iterable[String])] = stuRDD.groupBy(stuLine => stuLine.substring(stuLine.indexOf("bd")))(
ClassTag.Object.asInstanceOf[ClassTag[String]]
)
gbRDD.foreach{case (clazz, infos) => {
println(s"${clazz} ===> ${infos}")
}}
}
1.2.8. reduceByKey
按照key执行reduce操作,也要求数据类型(K, V) —> (K, V)
国际惯例的案例:
def rbkOps(sc: SparkContext): Unit = {
val list = List(
"yan xin xin",
"zhai zhao pin"
)
val listRDD = sc.parallelize(list)
val wordsRDD = listRDD.flatMap(line => line.split("\\s+"))
val pairsRDD:RDD[(String, Int)]= wordsRDD.map((_, 1))
val rbkRDD:RDD[(String, Int)] = pairsRDD.reduceByKey(_+_)
rbkRDD.foreach(println)
}
1.2.9. distinct
去重,及时sql中的关键字distinct。
def distinctOps(sc:SparkContext): Unit = {
val list = List(
"蔡金廷",
"蔡金廷",
"蔡金廷",
"蔡金廷"
)
val listRDD:RDD[String] = sc.parallelize(list)
//numPartitions:在去重的时候指定几个分区-->task,也就是说同时运行几个task来进行去重
listRDD.distinct(4).foreach(println)
}
1.2.10. sortByKey
以后会详细说
1.2.11 combineByKey ***
我们刚才通过查看源码,发现reduceByKey和groupByKey底层都是通过combineByKeyWithClassTag来实现的,有一个简写方式combineByKey。
意为按照key进行聚合combine操作,聚合操作有很多,groupBy、reduceBy、count等等。
自定义的combineByKey。通过学习combineBykey模拟reduceByKey和groupByKey来理解什么是分布式计算。
- combineByKey模拟reduceByKey
object _02CombineByKey2ReduceByKeyOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("cbk2rbk")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val list = List(
"hello you hello me",
"hello you shit me",
"hello you oh shit",
"me you hello me"
)
val listRDD = sc.parallelize(list)
val pairsRDD:RDD[(String, Int)] = listRDD.flatMap(_.split("\\s+")).map((_, 1))
//reduceByKey
println("-----------传统的rbk操作--------------")
pairsRDD.reduceByKey(_+_).foreach(println)
println("-----------combineByKey模拟rbk操作--------------")
pairsRDD.combineByKey(createCombiner, mergeValue, mergeCombiners)
.foreach(println)
sc.stop()
}
/**求:1 + 10
* var sum1 = 1
*
* for(i <- 2 to 10) {
* sum1 = sum1 + i
* }
* var sum2 = 0
* for(i <- 1 to 10) {
* sum2 = sum2 + i
* }
* 初始化聚合的结果类型
*/
def createCombiner(value:Int):Int = {
value
}
//分区内的聚合操作(map端的操作)
def mergeValue(sum:Int, value:Int):Int = {
sum + value
}
//分区间的聚合操作(reduce端的操作)
def mergeCombiners(sum1:Int, sum2:Int): Int = {
sum1 + sum2
}
}
- combineByKey模拟groupByKey
object _03CombineByKey2GroupByKeyOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("cbk2gbk")
.setMaster("local[2]")//thread -- task--->partition(block)
val sc = new SparkContext(conf)
val stu = List(
"1 刘梦男 22 bd-1901-bj",
"2 常国龙 25 bd-1901-bj",
"8 汪锦奇 24 bd-1901-sz",
"3 张湟熹 24 bd-1901-sz",
"4 胡盼盼(男) 18 bd-1901-wh",
"6 陈抒学 25 bd-1901-bj",
"7 曹煜 25 bd-1901-bj",
"5 周联动 18 bd-1901-wh",
"4 张震 18 bd-1901-sz"
)
//按照班级进行分组 -->sql中的groupBy
val stuRDD:RDD[String] = sc.parallelize(stu)
val class2Info:RDD[(String, String)] = stuRDD.map(stuLine => {
val clazz = stuLine.substring(stuLine.indexOf("bd"))
(clazz, stuLine)
})
class2Info.saveAsTextFile("file:///E:/data/out/cbk")
val gbkRDD:RDD[(String, Iterable[String])] = class2Info.groupByKey()
println("-----------传统的gbk操作--------------")
gbkRDD.foreach{case (clazz, infos) => {
println(s"${clazz} ---> ${infos}")
}}
println("-----------combineByKey模拟gbk操作--------------")
class2Info.combineByKey(
(info:String) => createCombiner(info),
(buffer:ArrayBuffer[String], info:String) => mergeValue(buffer, info),
(buffer1:ArrayBuffer[String], buffer2:ArrayBuffer[String]) => mergeCombiners(buffer1, buffer2),
new MyPartitioner(3))//该分区动作不是combineByKey数据输入进来是对数据做分区,经过combineByKey计算完毕之后将结果做分区
.saveAsTextFile("file:///E:/data/out/cbk1")
sc.stop()
}
/*
初始化操作,确定聚合操作之后的结果类型
在每一个分区内相同的key,需要调用一次该操作,并将其中的一个元素用于初始化操作
*/
def createCombiner(info:String):ArrayBuffer[String] = {
println("----createCombiner------>info: " + info)
val ab = ArrayBuffer[String]()
ab.append(info)
ab
}
/**
* 分区内的相同key的聚合操作
*
*/
def mergeValue(ab:ArrayBuffer[String], info:String):ArrayBuffer[String] = {
println(s"----mergeValue------>ab:${ab.mkString(",")}《===》info: ${info}")
ab.append(info)
ab
}
/**
* 分区间的相同key的聚合操作
*/
def mergeCombiners(ab1:ArrayBuffer[String], ab2:ArrayBuffer[String]):ArrayBuffer[String] = {
println(s"----mergeCombiners------>ab1:${ab1.mkString(",")}《===》ab2:${ab2.mkString(",")}")
ab1 ++ ab2
}
}
class MyPartitioner(partitions:Int) extends Partitioner {
override def numPartitions = partitions
override def getPartition(key: Any) = {
val ret = key match {
case clazz:String => {
//bd-1901-wh
val city = clazz.substring(clazz.lastIndexOf("-") + 1)
city match {
case "wh" => 0
case "bj" => 1
case "sz" => 2
}
}
case _ => 0
}
ret
}
}
1.2.12. aggregateByKey ***
aggregateBykey本质和combineByKey一样的。使用方面,也就是参数传递的方面略有差异。
- aggregateBykey模拟groupByKey
object _04AggregateByKey2GroupByKeyOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("abk2gbk")
.setMaster("local[2]")
//thread -- task--->partition(block)
val sc = new SparkContext(conf)
val stu = List(
"1 刘梦男 22 bd-1901-bj",
"2 常国龙 25 bd-1901-bj",
"8 汪锦奇 24 bd-1901-sz",
"3 张湟熹 24 bd-1901-sz",
"4 胡盼盼(男) 18 bd-1901-wh",
"6 陈抒学 25 bd-1901-bj",
"7 曹煜 25 bd-1901-bj",
"5 周联动 18 bd-1901-wh",
"4 张震 18 bd-1901-sz"
)
//按照班级进行分组 -->sql中的groupBy
val stuRDD: RDD[String] = sc.parallelize(stu)
val class2Info: RDD[(String, String)] = stuRDD.map(stuLine => {
val clazz = stuLine.substring(stuLine.indexOf("bd"))
(clazz, stuLine)
})
val gbkRDD: RDD[(String, Iterable[String])] = class2Info.groupByKey()
println("-----------传统的gbk操作--------------")
gbkRDD.foreach { case (clazz, infos) => {
println(s"${clazz} ---> ${infos}")
}
}
println("-----------aggregateByKey模拟gbk操作--------------")
val abk = class2Info.aggregateByKey(ArrayBuffer[String]())(seqOp, combOp)
abk.foreach(println)
}
/**
* var sum = 0
* for(i <- 1 to 10) {
* sum = sum + i
* }
* var sum1 = 1
* for(i <- 2 to 10) {
* sum = sum + i
* }
*/
def seqOp(ab:ArrayBuffer[String], info:String): ArrayBuffer[String] = {
ab.append(info)
ab
}
def combOp(ab1:ArrayBuffer[String], ab2:ArrayBuffer[String]): ArrayBuffer[String] = {
ab1 ++ ab2
}
}
- aggregateBykey模拟reduceByKey
object _05AggregateByKey2ReduceByKeyOps {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName("abk2rbk")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val list = List(
"hello you hello me",
"hello you shit me",
"hello you oh shit",
"me you hello me"
)
val listRDD = sc.parallelize(list)
val pairsRDD:RDD[(String, Int)] = listRDD.flatMap(_.split("\\s+")).map((_, 1))
//reduceByKey
println("-----------传统的rbk操作--------------")
pairsRDD.reduceByKey(_+_).foreach(println)
println("-----------combineByKey模拟rbk操作--------------")
pairsRDD.aggregateByKey(0)(seqOp, combOp).foreach(println)
sc.stop()
}
/**
* var sum = 0
* for(i <- 1 to 10) {
* sum = sum + i
* }
* var sum1 = 1
* for(i <- 2 to 10) {
* sum = sum + i
* }
*/
def seqOp(sum:Int, i:Int): Int = {
sum + i
}
def combOp(sum1:Int, sum2:Int): Int = {
sum1 + sum2
}
}
combineByKey和aggregateByKey的区别?
1、本质上二者是没有区别的,都是combineByKeyWithClassTag的简写方式,都是进行聚合操作。
2、combineByKey接收三个聚合参数,会将每个分区中相同key的第一个值用于初始化,而aggregateByKey接收一个0值。所以在分区内进行聚合操作的时候,combineByKey会被调用n-1次方法,aggregateByKey会被调用n次,其中n就是一个分区内相同key的数据量。
3、aggregateByKey其实更加适合处理那些聚合前后类型都一致的操作,combineByKey比较适合处理那些聚合前后类型不一致的操作。
pairsRDD.aggregateByKey(0)(_+_, _+_).foreach(println)
pairsRDD.combineByKey((num:Int) => num, (sum1:Int, num:Int) => sum1 + num, (sum:Int, sum1:Int) => sum + sum1)
1.3. Action
action操作,是行动算子,是触发spark作业执行的动因。
1.3.1. foreach
略
1.3.2. collect
搜集所有节点上的数据,开销较大,谨慎使用
1.3.3. count
略
1.3.4. saveAsTextFile/saveAsHadoopFile/saveAsHadoopAPIFile
/*
saveAsTextFile
将rdd以普通文本的方式进行存储,存储的格式()
*/
// retRDD.saveAsTextFile("file:///E:/data/out/astf")
/*
saveAsSequenceFile
将rdd以sequenceFile的格式存储
*/
// retRDD.saveAsSequenceFile("file:///E:/data/out/sequ", Option(classOf[DefaultCodec]))
/*
saveAsHadoopFile
saveAsNewHadoopAPIFile
都是使用的hadoop的格式化输出OutputFormat 这两个方法的区别主要就是OutputFormat的区别
saveAsHadoopFile --> org.apache.hadoop.mapred.OutputFormat
saveAsNewHadoopAPIFile --> org.apache.hadoop.mapreduce.OutputFormat
*/
retRDD.saveAsNewAPIHadoopFile("file:///E:/data/out/hadoop",
classOf[Text],
classOf[IntWritable],
classOf[TextOutputFormat[Text, IntWritable]]
)
1.3.5. take(n)/frist/takeOrdered
take获取集合中的前N个值,take(1)=first,如果说这个集合是有序的,take(n)—>TopN
println("-------take(2)-------------")
retRDD.take(2).foreach(println)
println("-------first-------------")
println(retRDD.first())
println("--------takeOrdered----------------------")
implicit val order = new Ordering[(String, Int)](){
override def compare(x: (String, Int), y: (String, Int)) = {
var ret = y._2.compareTo(x._2)
if(ret == 0) {
ret = y._1.compareTo(x._1)
}
ret
}
}
retRDD.takeOrdered(5).foreach(println)
1.3.6. countByKey
countByKey/
//通过每个key出现的次数
val k2counts = pairsRDD.countByKey()
for((k, v) <- k2counts) {
println(s"${k}---$v")
}
//使用groupByKey、reduceByKey、countByKey --->wordcount
println("-----------------------------------")
val gbks = pairsRDD.groupByKey()
// gbks.foreach(println)
gbks.map{case (key, values) => (key, values.size)}.foreach(println)
1.3.7. reduce
reduceByKey是一个transformation,reduce是一个action。
val sum = pairsRDD.values.reduce(_+_)
println(sum)
1.3. RDD持久化操作
1.3.1. why
为什么要有rdd的持久化的操作?
主要原因在于,如果我们相对一个RDD进行复用操作的时候,基于RDD的特性,当以rdd通过transformation转化为另外一个rdd的时候,前面的rdd就会被自动释放,此时还想在原来的rdd身上进行其它操作,需要从源头进行数据计算,这样效率自然会降低。为了能够在rdd重用的时候,直接从内存中加载相关数据,所以我们需要缓存算子(persist/cache)将rdd数据持久化到内存等等其它地方。
1.3.2 how
spark中对RDD的持久化操作,需要通过rdd.persist(StorageLevel)/cache方法来进行实现,使用完毕之后我们可以通过调用rdd.unPersist(blocking:Boolean) (阻塞)。
1.3.3. 持久化级别
StorageLevel:持久化级别,spark默认的持久化级别是MEMORY_ONLY
持久化级别 | 解释 |
MEMORY_ONLY | RDD中所有的数据都会以未经序列化的java对象的格式优先存储在内存中,如果内存不够,剩下的数据不会进行持久化。很容易出现OOM=OutOfMemoryException异常。java的gc频率和对象个数成正比。gc的时候会stop-the-world。 |
MEMORY_ONLY_SER | 和MEMORY_ONLY的操作几乎一致,唯一的区别是在内存中存储的不在是未经序列化的java对象,是序列化之后的数据,rdd经过序列化之后,每一个partition就只有一个字节数组,也就是说一个partition就是一个java对象。 |
MEMORY_AND_DISK | 和MEMORY_ONLY的唯一区别在于,MEMORY_ONLY不会持久化哪些在内存中持久化的数据,MEMORY_AND_DISK会将哪些在内存中保存不下的数据保存到磁盘中。 |
MEMORY_AND_DISK_SER | 就比MEMORY_AND_DISK多了一点,存储的是序列化的java对象 |
DISK_ONLY | 不好意思,不用 |
DISK_ONLY_SER | 不好意思,不用 |
XXXXX_2(MEMORY_ONLY_2等等) | 是在上述所有操作的基础之上进行了一个备份。从安全、高可用的角度上考虑,如果备份所消耗的时间,比数据丢失之后从源头重新计算一遍的代价小,我们才考虑使用Xxxx_2。 |
OFF_HEAP | 非堆。上述所有的操作都会使用Spark自身的内存资源,所以为了给计算提供足够的资源,可以将持久化的数据保存到非executor中。常见的OFF_HEAP:Tachyon/Alluxio |
1.3.4. 案例
object _07PersistApp {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${_07PersistApp.getClass.getSimpleName}")
.setMaster("local[*]")
val sc = new SparkContext(conf)
var start = System.currentTimeMillis()
val listRDD:RDD[String] = sc.textFile("file:///E:/data/spark/core")
listRDD.persist(StorageLevel.MEMORY_ONLY)
var retRDD:RDD[(String, Int)] = listRDD.flatMap(_.split("\\s+")).map((_, 1)).reduceByKey(_+_)
println("#######count's: " + retRDD.count())
var end = System.currentTimeMillis()
println("消耗时间:" + (end - start) + " ms")
println("-----------------------------------------")
start = System.currentTimeMillis()
retRDD = listRDD.flatMap(_.split("\\s+")).map((_, 1)).reduceByKey(_+_)
println("#######count's: " + retRDD.count())
end = System.currentTimeMillis()
println("消耗时间:" + (end - start) + " ms")
sc.stop()
}
}