目录
2.1.4 mapPartitions(List[T] ⇒ List[U])
2.1.7 sample(withReplacement, fraction, seed)
2.1.10 subtract(other, numPartitions)
2.1.11 distinct(numPartitions)
2.1.12 reduceByKey((V, V) ⇒ V, numPartition)
2.1.16 foldByKey(zeroValue)((V, V) ⇒ V)
2.1.17 join(other, numPartitions)
2.1.18 cogroup(other, numPartitions)
2.1.20 sortBy(ascending, numPartitions)
2.1.21 partitionBy(partitioner)
2.1.22 coalesce(numPartitions)
2.1.23 repartition(numPartitions)
2.1.24 repartitionAndSortWithinPartitions
2.2.6 takeSample(withReplacement, fract)
2.2.7 fold(zeroValue)( (T, T) ⇒ U )
2.2.9 saveAsSequenceFile(path)
2.RDD算子
目标
理解 RDD 的算子分类, 以及其特性
理解常见算子的使用
分类
RDD 中的算子从功能上分为两大类
Transformation(转换) 它会在一个已经存在的 RDD 上创建一个新的 RDD, 将旧的 RDD 的数据转换为另外一种形式后放入新的 RDD(笔记:转换算子的本质其实就是生成各种rdd,让rdd之间具有联系,只是生成rdd链条,执行到转换的时候并不会执行整个程序,而是在Action被调用的时候,程序才会执行)
Action(动作) 执行各个分区的计算任务, 将的到的结果返回到 Driver 中
RDD 中可以存放各种类型的数据, 那么对于不同类型的数据, RDD 又可以分为三类
针对基础类型(例如 String)处理的普通算子
针对
Key-Value
数据处理的byKey
算子针对数字类型数据处理的计算算子
特点
Spark 中所有的 Transformations 是 Lazy(惰性) 的, 它们不会立即执行获得结果. 相反, 它们只会记录在数据集上要应用的操作. 只有当需要返回结果给 Driver 时, 才会执行这些操作, 通过 DAGScheduler 和 TaskScheduler 分发到集群中运行, 这个特性叫做 惰性求值
默认情况下, 每一个 Action 运行的时候, 其所关联的所有 Transformation RDD 都会重新计算, 但是也可以使用
presist
方法将 RDD 持久化到磁盘或者内存中. 这个时候为了下次可以更快的访问, 会把数据保存到集群上.
2.1 Transformations 算子
2.1.1 map: map(T ⇒ U)
用法:
sc.parallelize(Seq(1, 2, 3))
.map( num => num * 10 )
.collect()
作用
- 把 RDD 中的数据 一对一 的转为另一种形式
签名
def map[U: ClassTag](f: T ⇒ U): RDD[U]
参数
-
f
→ Map 算子是原RDD → 新RDD
的过程, 传入函数的参数是原 RDD 数据, 返回值是经过函数转换的新 RDD 的数据
注意点
-
Map 是一对一, 如果函数是
String → Array[String]
则新的 RDD 中每条数据就是一个数组
2.1.2 flatMap(T ⇒ List[U])
用法:
sc.parallelize(Seq("Hello lily", "Hello lucy", "Hello tim"))
.flatMap( line => line.split(" ") )
.collect()
作用
-
FlatMap 算子和 Map 算子类似, 但是 FlatMap 是一对多
调用
def flatMap[U: ClassTag](f: T ⇒ List[U]): RDD[U]
参数
-
f
→ 参数是原 RDD 数据, 返回值是经过函数转换的新 RDD 的数据, 需要注意的是返回值是一个集合, 集合中的数据会被展平后再放入新的 RDD
注意点
-
flatMap 其实是两个操作, 是
map + flatten
, 也就是先转换, 后把转换而来的 List 展开 -
Spark 中并没有直接展平 RDD 中数组的算子, 可以使用
flatMap
做这件事
2.1.3 filter(T ⇒ Boolean)
用法:
/**
* filter可以过滤掉数据集中一部分元素
* filter中接收的函数,参数是每一个元素,如果这个函数返回true,当前元素会被加入,
* 如果这个函数返回flase,当前元素会被过滤.
*/
@Test
def filter():Unit = {
//1.定义集合
//2.过滤数据
//3.收集结果
sc.parallelize(Seq(1,2,3,4,5,6,7,8,9,10))
.filter(item => item%2==0)
.collect()
.foreach(item => println(item))
}
//运行结果:2 4 6 8 10
作用
-
Filter
算子的主要作用是过滤掉不需要的内容
2.1.4 mapPartitions(List[T] ⇒ List[U])
用法:
@Test
def mapPartitions2(): Unit = {
//1.数据生成
//2.算子使用
//3.获取结果
sc.parallelize(Seq(1, 2, 3, 4, 5, 6), 2)
.mapPartitions(iter => {
//遍历iter其中每一条数据进行转换,转换完成以后,返回这个iter
//iter 是scala中的集合类型
iter.map(iter => iter*10)
})
.collect()
.foreach(x => print(x + " "))
}
//运行结果:10 20 30 40 50 60
作用:
- RDD[T] ⇒ RDD[U] 和 map 类似, 但是针对整个分区的数据转换
2.1.5 mapPartitionsWithIndex
用法:
/**
* mapPartitions 和 mapPartitionsWithIndex的区别是func中多了一个参数,是分区号
*/
@Test
def mapPartitionsWithIndex(): Unit={
sc.parallelize(Seq(1,2,3,4,5,6),2)
.mapPartitionsWithIndex((index,iter) =>{
println("index: " + index )
iter.foreach(item => println(item))
iter
})
.collect()
}
//运行结果: index: 0 index: 1
//4 5 1 6 2 3
作用:
- 和 mapPartitions 类似, 只是在函数中增加了分区的 Index
2.1.6 mapValues
用法:
/**
* mapValue 也是 map, 只不过map作用于整条数据, mapValue 作用于 Value
*/
@Test
def mapValues(): Unit = {
sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3)))
.mapValues( item => item * 10 )
.collect()
.foreach(println(_))
}
//运行结果:(a,10) (b,20) (c,30)
作用
-
MapValues 只能作用于 Key-Value 型数据, 和 Map 类似, 也是使用函数按照转换数据, 不同点是 MapValues 只转换 Key-Value 中的 Value
2.1.7 sample(withReplacement, fraction, seed)
用法:
/**
* 作用: 把大数据集变小, 尽可能的减少数据集规律的损失
* withReplacement: 指定为True的情况下, 可能重复, 如果是Flase, 无重复
*/
@Test
def sample(): Unit = {
//1.定义集合
//2.过滤数据
//3.收集结果
val rdd1 = sc.parallelize(Seq(1,2,3,4,5,6,7,8,9,10))
val rdd2 = rdd1.sample(false,0.6)
val result = rdd2.collect()
result.foreach(item => println(item))
}
//运行结果:1 5 6 7 8
作用
-
Sample 算子可以从一个数据集中抽样出来一部分, 常用作于减小数据集以保证运行速度, 并且尽可能少规律的损失
参数
-
Sample 接受第一个参数为`withReplacement`, 意为是否取样以后是否还放回原数据集供下次使用, 简单的说, 如果这个参数的值为 true, 则抽样出来的数据集中可能会有重复
-
Sample 接受第二个参数为`fraction`, 意为抽样的比例
-
Sample 接受第三个参数为`seed`, 随机数种子, 用于 Sample 内部随机生成下标, 一般不指定, 使用默认值
2.1.8 union(other)
用法:
/**
* 并集 可以重复的
*/
@Test
def union(): Unit={
val rdd1 = sc.parallelize(Seq(1,2,3,4,5))
val rdd2 = sc.parallelize(Seq(3,4,5,6,7))
rdd1.union(rdd2)
.collect()
.foreach(println(_))
}
//运行结果:1,2,3,4,5,3,4,5,6,7
作用:
- 取并集
2.1.9 intersection(other)
用法:
/**
* 交集 3,4,5
*/
@Test
def intersection(): Unit={
val rdd1 = sc.parallelize(Seq(1,2,3,4,5))
val rdd2 = sc.parallelize(Seq(3,4,5,6,7))
rdd1.intersection(rdd2)
.collect()
.foreach(println(_))
}
//运行结果:3,4,5
作用
-
Intersection 算子是一个集合操作, 用于求得 左侧集合 和 右侧集合 的交集, 换句话说, 就是左侧集合和右侧集合都有的元素, 并生成一个新的 RDD
2.1.10 subtract(other, numPartitions)
用法:
/**
* 差集 1,2
*/
@Test
def subtract(): Unit={
val rdd1 = sc.parallelize(Seq(1,2,3,4,5))
val rdd2 = sc.parallelize(Seq(3,4,5,6,7))
rdd1.subtract(rdd2)
.collect()
.foreach(println(_))
}
//运行结果:1,2
作用:
- (RDD[T], RDD[T]) ⇒ RDD[T] 差集, 可以设置分区数
2.1.11 distinct(numPartitions)
用法:
sc.parallelize(Seq(1, 1, 2, 2, 3))
.distinct()
.collect()
作用
-
Distinct 算子用于去重
注意点
-
Distinct 是一个需要 Shuffled 的操作
-
本质上 Distinct 就是一个 reductByKey, 把重复的合并为一个
2.1.12 reduceByKey((V, V) ⇒ V, numPartition)
用法:
sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
.reduceByKey( (curr, agg) => curr + agg )
.collect()
作用
-
首先按照 Key 分组生成一个 Tuple, 然后针对每个组执行
reduce
算子
调用
def reduceByKey(func: (V, V) ⇒ V): RDD[(K, V)]
参数
-
func → 执行数据处理的函数, 传入两个参数, 一个是当前值, 一个是局部汇总, 这个函数需要有一个输出, 输出就是这个 Key 的汇总结果
注意点
-
ReduceByKey 只能作用于 Key-Value 型数据, Key-Value 型数据在当前语境中特指 Tuple2
-
ReduceByKey 是一个需要 Shuffled 的操作
-
和其它的 Shuffled 相比, ReduceByKey是高效的, 因为类似 MapReduce 的, 在 Map 端有一个 Cominer, 这样 I/O 的数据便会减少
2.1.13 groupByKey()
用法:
sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
.groupByKey()
.collect()
作用
-
GroupByKey 算子的主要作用是按照 Key 分组, 和 ReduceByKey 有点类似, 但是 GroupByKey 并不求聚合, 只是列举 Key 对应的所有 Value
注意点
-
GroupByKey 是一个 Shuffled
-
GroupByKey 和 ReduceByKey 不同, 因为需要列举 Key 对应的所有数据, 所以无法在 Map 端做 Combine, 所以 GroupByKey 的性能并没有 ReduceByKey 好
2.1.14 combineByKey()
用法:
val rdd = sc.parallelize(Seq(
("zhangsan", 99.0),
("zhangsan", 96.0),
("lisi", 97.0),
("lisi", 98.0),
("zhangsan", 97.0))
)
val combineRdd = rdd.combineByKey(
score => (score, 1),
(scoreCount: (Double, Int),newScore) => (scoreCount._1 + newScore, scoreCount._2 + 1),
(scoreCount1: (Double, Int), scoreCount2: (Double, Int)) =>
(scoreCount1._1 + scoreCount2._1, scoreCount1._2 + scoreCount2._2)
)
val meanRdd = combineRdd.map(score => (score._1, score._2._1 / score._2._2))
meanRdd.collect()
作用
-
对数据集按照 Key 进行聚合
调用
-
combineByKey(createCombiner, mergeValue, mergeCombiners, [partitioner], [mapSideCombiner], [serializer])
参数
-
createCombiner
将 Value 进行初步转换 -
mergeValue
在每个分区把上一步转换的结果聚合 -
mergeCombiners
在所有分区上把每个分区的聚合结果聚合 -
partitioner
可选, 分区函数 -
mapSideCombiner
可选, 是否在 Map 端 Combine -
serializer
序列化器
注意点
-
combineByKey
的要点就是三个函数的意义要理解 -
groupByKey
,reduceByKey
的底层都是combineByKey
2.1.15 aggregateByKey()
用法:
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.aggregateByKey(0.8)(
seqOp = (zero, price) => price * zero,
combOp = (curr, agg) => curr + agg
).collect()
println(result)
作用
-
聚合所有 Key 相同的 Value, 换句话说, 按照 Key 聚合 Value
调用
-
rdd.aggregateByKey(zeroValue)(seqOp, combOp)
参数
-
zeroValue
初始值 -
seqOp
转换每一个值的函数 (作用于每一条数据) -
comboOp
将转换过的值聚合的函数 (把整体聚合,生成最终的结果)
注意点 * 为什么需要两个函数? aggregateByKey 运行将一个`RDD[(K, V)]聚合为`RDD[(K, U)]
, 如果要做到这件事的话, 就需要先对数据做一次转换, 将每条数据从`V`转为`U`, `seqOp`就是干这件事的 ** 当`seqOp`的事情结束以后, `comboOp`把其结果聚合
-
和 reduceByKey 的区别::
-
aggregateByKey 最终聚合结果的类型和传入的初始值类型保持一致
-
reduceByKey 在集合中选取第一个值作为初始值, 并且聚合过的数据类型不能改变
-
2.1.16 foldByKey(zeroValue)((V, V) ⇒ V)
用法:
sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1)))
.foldByKey(zeroValue = 10)( (curr, agg) => curr + agg )
.collect()
作用
-
和 ReduceByKey 是一样的, 都是按照 Key 做分组去求聚合, 但是 FoldByKey 的不同点在于可以指定初始值
调用
foldByKey(zeroValue)(func)
参数
-
zeroValue
初始值 -
func
seqOp 和 combOp 相同, 都是这个参数
注意点
-
FoldByKey 是 AggregateByKey 的简化版本, seqOp 和 combOp 是同一个函数
-
FoldByKey 指定的初始值作用于每一个 Value
2.1.17 join(other, numPartitions)
作用:
val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("b", 1)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("a", 11), ("a", 12)))
rdd1.join(rdd2).collect()
作用
-
将两个 RDD 按照相同的 Key 进行连接
调用
join(other, [partitioner or numPartitions])
参数
-
other
其它 RDD -
partitioner or numPartitions
可选, 可以通过传递分区函数或者分区数量来改变分区
注意点
-
Join 有点类似于 SQL 中的内连接, 只会再结果中包含能够连接到的 Key
-
Join 的结果是一个笛卡尔积形式, 例如`"a", 1), ("a", 2
和
"a", 10), ("a", 11的 Join 结果集是 `"a", 1, 10), ("a", 1, 11), ("a", 2, 10), ("a", 2, 11)
2.1.18 cogroup(other, numPartitions)
用法:
val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("a", 5), ("b", 2), ("b", 6), ("c", 3), ("d", 2)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("b", 1), ("d", 3)))
val rdd3 = sc.parallelize(Seq(("b", 10), ("a", 1)))
val result1 = rdd1.cogroup(rdd2).collect()
val result2 = rdd1.cogroup(rdd2, rdd3).collect()
/*
执行结果:
Array(
(d,(CompactBuffer(2),CompactBuffer(3))),
(a,(CompactBuffer(1, 2, 5),CompactBuffer(10))),
(b,(CompactBuffer(2, 6),CompactBuffer(1))),
(c,(CompactBuffer(3),CompactBuffer()))
)
*/
println(result1)
/*
执行结果:
Array(
(d,(CompactBuffer(2),CompactBuffer(3),CompactBuffer())),
(a,(CompactBuffer(1, 2, 5),CompactBuffer(10),CompactBuffer(1))),
(b,(CompactBuffer(2, 6),CompactBuffer(1),Co...
*/
println(result2)
作用
-
多个 RDD 协同分组, 将多个 RDD 中 Key 相同的 Value 分组
调用
-
cogroup(rdd1, rdd2, rdd3, [partitioner or numPartitions])
参数
-
rdd…
最多可以传三个 RDD 进去, 加上调用者, 可以为四个 RDD 协同分组 -
partitioner or numPartitions
可选, 可以通过传递分区函数或者分区数来改变分区
注意点
-
对 RDD1, RDD2, RDD3 进行 cogroup, 结果中就一定会有三个 List, 如果没有 Value 则是空 List, 这一点类似于 SQL 的全连接, 返回所有结果, 即使没有关联上
-
CoGroup 是一个需要 Shuffled 的操作
2.1.19 cartesian(other)
(RDD[T], RDD[U]) ⇒ RDD[(T, U)] 生成两个 RDD 的笛卡尔积
2.1.20 sortBy(ascending, numPartitions)
用法:
val rdd1 = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val sortByResult = rdd1.sortBy( item => item._2 ).collect()
val sortByKeyResult = rdd1.sortByKey().collect()
println(sortByResult)
println(sortByKeyResult)
作用
-
排序相关相关的算子有两个, 一个是`sortBy`, 另外一个是`sortByKey`
调用
sortBy(func, ascending, numPartitions)
参数
-
`func`通过这个函数返回要排序的字段
-
`ascending`是否升序
-
`numPartitions`分区数
注意点
-
普通的 RDD 没有`sortByKey`, 只有 Key-Value 的 RDD 才有
-
`sortBy`可以指定按照哪个字段来排序, `sortByKey`直接按照 Key 来排序
2.1.21 partitionBy(partitioner)
使用用传入的 partitioner 重新分区, 如果和当前分区函数相同, 则忽略操作
2.1.22 coalesce(numPartitions)
用法:减少分区数
val rdd = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val oldNum = rdd.partitions.length
val coalesceRdd = rdd.coalesce(4, shuffle = true)
val coalesceNum = coalesceRdd.partitions.length
val repartitionRdd = rdd.repartition(4)
val repartitionNum = repartitionRdd.partitions.length
print(oldNum, coalesceNum, repartitionNum)
作用
-
一般涉及到分区操作的算子常见的有两个,
repartitioin
和coalesce
, 两个算子都可以调大或者调小分区数量
调用
-
repartitioin(numPartitions)
-
coalesce(numPartitions, shuffle)
参数
-
numPartitions
新的分区数 -
shuffle
是否 shuffle, 如果新的分区数量比原分区数大, 必须 Shuffled, 否则重分区无效
注意点
-
repartition
和coalesce
的不同就在于coalesce
可以控制是否 Shuffle -
repartition
是一个 Shuffled 操作
2.1.23 repartition(numPartitions)
重新分区
2.1.24 repartitionAndSortWithinPartitions
重新分区的同时升序排序, 在partitioner中排序, 比先重分区再排序要效率高, 建议使用在需要分区后再排序的场景使用
2.2 Action 算子
2.2.1 reduce( (T, T) ⇒ U )
用法:
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.reduce((curr, agg) => ("总价", curr._2 + agg._2))
println(result)
作用
-
对整个结果集规约, 最终生成一条数据, 是整个数据集的汇总
调用
-
reduce( (currValue[T], agg[T]) ⇒ T )
注意点
-
reduce 和 reduceByKey 是完全不同的, reduce 是一个 action, 并不是 Shuffled 操作
-
本质上 reduce 就是现在每个 partition 上求值, 最终把每个 partition 的结果再汇总
笔记
reduceByKey和reduce有什么区别?
1.reduce是一个Action算子,redueceByKey是一个转换算子
2.假设一个RDD里面有1万条数据,大部分key相同,有10个不同的key
rdd.reduceByKey生成10条数据
rdd.reduce生成1条结果
reduceByKey本质上是先按照Key分组,然后把每组聚合
reduce是针对于一整个数据集来进行聚合
3.reduceByKey是针对KV型数据来进行计算,reduce可以针对所有类型的数据进行计算
reduce算子是一个Shuffle操作吗?
*Shuffle操作分为mapper和reduce,mapper将数据放入paritioner的函数计算,求得分往哪一个reducer,再分到对应的reducer中(多对多)
*reduce操作并没有mapper和reducer,因为reduce算子会作用于RDD中的每一个分区,然后再分区上求得局部结果,最终汇总到Driver中求得最终结果(多对一)
RDD中有5大属性,Partitioner再Shuffe过程中使用
Partitioner只有KV型的RDD才有
2.2.2 collect()
以数组的形式返回数据集中所有元素
2.2.3 count()
返回元素个数
2.2.4 first()
返回第一个元素
2.2.5 take( N )
返回前 N 个元素
2.2.6 takeSample(withReplacement, fract)
类似于 sample, 区别在这是一个Action, 直接返回结果
2.2.7 fold(zeroValue)( (T, T) ⇒ U )
指定初始值和计算函数, 折叠聚合整个数据集
2.2.8 saveAsTextFile(path)
将结果存入 path 对应的文件中
2.2.9 saveAsSequenceFile(path)
将结果存入 path 对应的 Sequence 文件中
2.2.10 countByKey()
用法:
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.countByKey()
println(result)
作用
-
求得整个数据集中 Key 以及对应 Key 出现的次数
注意点
-
返回结果为
Map(key → count)
-
常在解决数据倾斜问题时使用, 查看倾斜的 Key
2.2.11 foreach( T ⇒ … )
遍历每一个元素
应用
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
// 结果: Array((手机,10.0), (手机,15.0), (电脑,20.0))
println(rdd.collect())
// 结果: Array((手机,10.0), (手机,15.0))
println(rdd.take(2))
// 结果: (手机,10.0)
println(rdd.first())
2.3 转换算子和动作算子总结
RDD 的算子大部分都会生成一些专用的 RDD
map
,flatMap
,filter
等算子会生成MapPartitionsRDD
coalesce
,repartition
等算子会生成CoalescedRDD
常见的 RDD 有两种类型
转换型的 RDD, Transformation
动作型的 RDD, Action
常见的 Transformation 类型的 RDD
map
flatMap
filter
groupBy
reduceByKey
常见的 Action 类型的 RDD
collect
countByKey
reduce
2.4 RDD 对不同类型数据的支持
目标
理解 RDD 对 Key-Value 类型的数据是有专门支持的
理解 RDD 对数字类型也有专门的支持
一般情况下 RDD 要处理的数据有三类
字符串
键值对
数字型
RDD 的算子设计对这三类不同的数据分别都有支持
对于以字符串为代表的基本数据类型是比较基础的一些的操作, 诸如 map, flatMap, filter 等基础的算子
对于键值对类型的数据, 有额外的支持, 诸如 reduceByKey, groupByKey 等 byKey 的算子
同样对于数字型的数据也有额外的支持, 诸如 max, min 等
RDD 对键值对数据的额外支持
键值型数据本质上就是一个二元元组, 键值对类型的 RDD 表示为
RDD[(K, V)]
RDD 对键值对的额外支持是通过隐式支持来完成的, 一个
RDD[(K, V)]
, 可以被隐式转换为一个PairRDDFunctions
对象, 从而调用其中的方法.既然对键值对的支持是通过
PairRDDFunctions
提供的, 那么从PairRDDFunctions
中就可以看到这些支持有什么
类别 算子 聚合操作
reduceByKey
foldByKey
combineByKey
分组操作
cogroup
groupByKey
连接操作
join
leftOuterJoin
rightOuterJoin
排序操作
sortBy
sortByKey
Action
countByKey
take
collect
RDD 对数字型数据的额外支持
对于数字型数据的额外支持基本上都是 Action 操作, 而不是转换操作
算子 含义
count
个数
mean
均值
sum
求和
max
最大值
min
最小值
variance
方差
sampleVariance
从采样中计算方差
stdev
标准差
sampleStdev
采样的标准差
val rdd = sc.parallelize(Seq(1, 2, 3)) // 结果: 3 println(rdd.max())
2.5 阶段练习和总结
导读
1.通过本节, 希望大家能够理解 RDD 的一般使用步骤
import org.apache.commons.lang.StringUtils
import org.apache.spark.{SparkConf, SparkContext}
import org.junit.Test
class StagePractice {
@Test
def pmProcess(): Unit ={
//1.创建sc对象
val conf = new SparkConf().setMaster("local[6]").setAppName("stage_practice")
val sc = new SparkContext(conf)
//2.读取文件
val source = sc.textFile("dataset/BeijingPM20100101_20151231_noheader.csv")
//3.通过算子处理数据
// 1.抽取数据 年 月 PM 返回结果:((年,月), PM)
// 2.清洗,过滤掉空的字符串,过滤掉NA
// 3.聚合
// 4.排序
val resultRDD = source.map( x => ((x.split(",")(1),x.split(",")(2)),x.split(",")(6)))
.filter( x => StringUtils.isNotEmpty(x._2) && !x._2.equalsIgnoreCase("NA"))
.map( x => (x._1,x._2.toInt))
.reduceByKey( (curr,agg) => curr + agg)
.sortBy( x => x._2, ascending = false)
//4.获取结果
resultRDD.take(10).foreach(x => println(x))
//5.运行测试
sc.stop()
}
}
通过上述代码可以看到, 其实 RDD 的整体使用步骤如下