Spark学习之路(四)—— RDD常用算子详解
一、Transformation
spark常用的Transformation算子如下表:
| Transformation算子 | Meaning(含义) |
|---|---|
| map(func) | 对原RDD中每个元素运用 func 函数,并生成新的RDD |
| filter(func) | 对原RDD中每个元素使用func 函数进行过滤,并生成新的RDD |
| flatMap(func) | 与 map 类似,但是每一个输入的 item 被映射成 0 个或多个输出的 items( func 返回类型需要为 Seq )。 |
| mapPartitions(func) | 与 map 类似,但函数单独在RDD的每个分区上运行, func函数的类型为 Iterator<T> => Iterator<U> ,其中T是RDD的类型,即RDD[T] |
| mapPartitionsWithIndex(func) | 与 mapPartitions 类似,但 func 类型为 (Int, Iterator<T>) => Iterator<U> ,其中第一个参数为分区索引 |
| sample(withReplacement, fraction, seed) | 数据采样,有三个可选参数:设置是否放回(withReplacement)、采样的百分比(fraction)、随机数生成器的种子(seed); |
| union(otherDataset) | 合并两个RDD |
| intersection(otherDataset) | 求两个RDD的交集 |
| distinct([numTasks])) | 去重 |
| groupByKey([numTasks]) | 按照key值进行分区,即在一个 (K, V) 对的 dataset 上调用时,返回一个 (K, Iterable<V>) Note: 如果分组是为了在每一个 key 上执行聚合操作(例如,sum 或 average),此时使用 reduceByKey 或 aggregateByKey 性能会更好Note: 默认情况下,并行度取决于父 RDD 的分区数。可以传入 numTasks 参数进行修改。 |
| reduceByKey(func, [numTasks]) | 按照key值进行分组,并对分组后的数据执行归约操作。 |
| aggregateByKey(zeroValue,numPartitions)(seqOp, combOp, [numTasks]) | 当调用(K,V)对的数据集时,返回(K,U)对的数据集,其中使用给定的组合函数和zeroValue聚合每个键的值。与groupByKey类似,reduce任务的数量可通过第二个参数进行配置。 |
| sortByKey([ascending], [numTasks]) | 按照key进行排序,其中的key需要实现Ordered特质,即可比较 |
| join(otherDataset, [numTasks]) | 在一个 (K, V) 和 (K, W) 类型的 dataset 上调用时,返回一个 (K, (V, W)) pairs 的 dataset,等价于内连接操作。如果想要执行外连接,可以使用leftOuterJoin, rightOuterJoin 和 fullOuterJoin 等算子。 |
| cogroup(otherDataset, [numTasks]) | 在一个 (K, V) 对的 dataset 上调用时,返回一个 (K, (Iterable<V>, Iterable<W>)) tuples 的 dataset。 |
| cartesian(otherDataset) | 在一个 T 和 U 类型的 dataset 上调用时,返回一个 (T, U) 类型的 dataset(即笛卡尔积)。 |
| coalesce(numPartitions) | 将RDD中的分区数减少为numPartitions。 |
| repartition(numPartitions) | 随机重新调整RDD中的数据以创建更多或更少的分区,并在它们之间进行平衡。 |
| repartitionAndSortWithinPartitions(partitioner) | 根据给定的 partitioner(分区器)对 RDD 进行重新分区,并对分区中的数据按照 key 值进行排序。这比调用 repartition 然后再 sorting(排序)效率更高,因为它可以将排序过程推送到 shuffle 操作所在的机器。 |
下面分别给出这些算子的基本使用示例:
1.1 map
val list = List(1,2,3)
sc.parallelize(list).map(_ * 10).foreach(println)
// 输出结果: 10 20 30 (这里为了节省篇幅去掉了换行,后文亦同)
1.2 filter
val list = List(3, 6, 9, 10, 12, 21)
sc.parallelize(list).filter(_ >= 10).foreach(println)
// 输出: 10 12 21
1.3 flatMap
flatMap(func)与map类似,但每一个输入的item会被映射成 0 个或多个输出的items( func 返回类型需要为Seq)。
val list = List(List(1, 2), List(3), List(), List(4, 5))
sc.parallelize(list).flatMap(_.toList).map(_ * 10).foreach(println)
// 输出结果 : 10 20 30 40 50
flatMap 这个算子在日志分析中使用概率非常高,这里进行一下演示:拆分输入的每行数据为单个单词,并赋值为1,代表出现一次,之后按照单词分组并统计其出现总次数,代码如下:
val lines = List("spark flume spark",
"hadoop flume hive")
sc.parallelize(lines).flatMap(line => line.split(" ")).
map(word=>(word,1)).reduceByKey(_+_).foreach(println)
// 输出:
(spark,2)
(hive,1)
(hadoop,1)
(flume,2)
1.4 mapPartitions
与 map 类似,但函数单独在RDD的每个分区上运行, func函数的类型为Iterator<T> => Iterator<U> (其中T是RDD的类型),即输入和输出都必须是可迭代类型。
val list = List(1, 2, 3, 4, 5, 6)
sc.parallelize(list, 3).mapPartitions(iterator => {
val buffer = new ListBuffer[Int]
while (iterator.hasNext) {
buffer.append(iterator.next() * 100)
}
buffer.toIterator
}).foreach(println)
//输出结果
100 200 300 400 500 600
1.5 mapPartitionsWithIndex
与 mapPartitions 类似,但 func 类型为(Int, Iterator<T>) => Iterator<U> ,其中第一个参数为分区索引。
val list = List(1, 2, 3, 4, 5, 6)
sc.parallelize(list, 3).mapPartitionsWithIndex((index, iterator) => {
val buffer = new ListBuffer[String]
while (iterator.hasNext) {
buffer.append(index + "分区:" + iterator.next() * 100)
}
buffer.toIterator
}).foreach(println)
//输出
0分区:100
0分区:200
1分区:300
1分区:400
2分区:500
2分区:600
1.6 sample
数据采样。有三个可选参数:设置是否放回(withReplacement)、采样的百分比(fraction)、随机数生成器的种子(seed) :
val list = List(1, 2, 3, 4, 5, 6)
sc.parallelize(list).sample(withReplacement = false, fraction = 0.5).foreach(println)
1.7 union
合并两个RDD:
val list1 = List(1, 2, 3)
val list2 = List(4, 5, 6)
sc.parallelize(list1).union(sc.parallelize(list2)).foreach(println)
// 输出: 1 2 3 4 5 6
1.8 intersection
求两个RDD的交集:
val list1 = List(1, 2, 3, 4, 5)
val list2 = List(4, 5, 6)
sc.parallelize(list1).intersection(sc.parallelize(list2)).foreach(println)
// 输出: 4 5
1.9 distinct
去重:
val list = List(1, 2, 2, 4, 4)
sc.parallelize(list).distinct().foreach(println)
// 输出: 4 1 2
1.10 groupByKey
按照键进行分组:
val list = List(("hadoop", 2), ("spark", 3), ("spark", 5), ("storm", 6), ("hadoop", 2))
sc.parallelize(list).groupByKey().map(x => (x._1, x._2.toList)).foreach(println)
//输出:
(spark,List(3, 5))
(hadoop,List(2, 2))
(storm,List(6))
1.11 reduceByKey
按照键进行归约操作:
val list = List(("hadoop", 2), ("spark", 3), ("spark", 5), ("storm", 6), ("hadoop", 2))
sc.parallelize(list).reduceByKey(_ + _).foreach(println)
//输出
(spark,8)
(hadoop,4)
(storm,6)
1.12 sortBy & sortByKey
按照键进行排序:
val list01 = List((100, "hadoop"), (90, "spark"), (120, "storm"))
sc.parallelize(list01).sortByKey(ascending = false).foreach(println)
// 输出
(120,storm)
(90,spark)
(100,hadoop)
按照指定元素进行排序:
val list02 = List(("hadoop",100), ("spark",90), ("storm",120))
sc.parallelize(list02).sortBy(x=>x._2,ascending=false).foreach(println)
// 输出
(storm,120)
(hadoop,100)
(spark,90)
1.13 join
在一个 (K, V) 和 (K, W) 类型的 Dataset 上调用时,返回一个 (K, (V, W)) 的 Dataset,等价于内连接操作。如果想要执行外连接,可以使用leftOuterJoin, rightOuterJoin 和 fullOuterJoin 等算子。
val list01 = List((1, "student01"), (2, "student02"), (3, "student03"))
val list02 = List((1, "teacher01"), (2, "teacher02"), (3, "teacher03"))
sc.parallelize(list01).join(sc.parallelize(list02)).foreach(println)
// 输出
(1,(student01,teacher01))
(3,(student03,teacher03))
(2,(student02,teacher02))
1.14 cogroup
在一个 (K, V) 对的 Dataset 上调用时,返回多个类型为 (K, (Iterable<V>, Iterable<W>)) 的元组所组成的Dataset。
val list01 = List((1, "a"),(1, "a"), (2, "b"), (3, "e"))
val list02 = List((1, "A"), (2, "B"), (3, "E"))
val list03 = List((1, "[ab]"), (2, "[bB]"), (3, "eE"),(3, "eE"))
sc.parallelize(list01).cogroup(sc.parallelize(list02),sc.parallelize(list03)).foreach(println)
// 输出: 同一个RDD中的元素先按照key进行分组,然后再对不同RDD中的元素按照key进行分组
(1,(CompactBuffer(a, a),CompactBuffer(A),CompactBuffer([ab])))
(3,(CompactBuffer(e),CompactBuffer(E),CompactBuffer(eE, eE)))
(2,(CompactBuffer(b),CompactBuffer(B),CompactBuffer([bB])))
1.15 cartesian
计算笛卡尔积:
val list1 = List("A", "B", "C")
val list2 = List(1, 2, 3)
sc.parallelize(list1).cartesian(sc.parallelize(list2)).foreach(println)
//输出笛卡尔积
(A,1)
(A,2)
(A,3)
(B,1)
(B,2)
(B,3)
(C,1)
(C,2)
(C,3)
1.16 aggregateByKey
当调用(K,V)对的数据集时,返回(K,U)对的数据集,其中使用给定的组合函数和zeroValue聚合每个键的值。与groupByKey类似,reduce任务的数量可通过第二个参数numPartitions进行配置。示例如下:
// 为了清晰,以下所有参数均使用具名传参
val list = List(("hadoop", 3), ("hadoop", 2), ("spark", 4), ("spark", 3), ("storm", 6), ("storm", 8))
sc.parallelize(list,numSlices = 2).aggregateByKey(zeroValue = 0,numPartitions = 3)(
seqOp = math.max(_, _),
combOp = _ + _
).collect.foreach(println)
//输出结果:
(hadoop,3)
(storm,8)
(spark,7)
这里使用了numSlices = 2指定aggregateByKey父操作parallelize的分区数量为2,其执行流程如下:

基于同样的执行流程,如果numSlices = 1,则意味着只有输入一个分区,则其最后一步combOp相当于是无效的,执行结果为:
(hadoop,3)
(storm,8)
(spark,4)
同样的,如果每个单词对一个分区,即numSlices = 6,此时相当于求和操作,执行结果为:
(hadoop,5)
(storm,14)
(spark,7)
aggregateByKey(zeroValue = 0,numPartitions = 3)的第二个参数numPartitions决定的是输出RDD的分区数量,想要验证这个问题,可以对上面代码进行改写,使用getNumPartitions方法获取分区数量:
sc.parallelize(list,numSlices = 6).aggregateByKey(zeroValue = 0,numPartitions = 3)(
seqOp = math.max(_, _),
combOp = _ + _
).getNumPartitions

二、Action
Spark常用的Action算子如下:
| Action(动作) | Meaning(含义) |
|---|---|
| reduce(func) | 使用函数func执行归约操作 |
| collect() | 以一个 array 数组的形式返回 dataset 的所有元素,适用于小结果集。 |
| count() | 返回 dataset 中元素的个数。 |
| first() | 返回 dataset 中的第一个元素,等价于 take(1)。 |
| take(n) | 将数据集中的前 n 个元素作为一个 array 数组返回。 |
| takeSample(withReplacement, num, [seed]) | 对一个 dataset 进行随机抽样 |
| takeOrdered(n, [ordering]) | 按自然顺序(natural order)或自定义比较器(custom comparator)排序后返回前 n 个元素。只适用于小结果集,因为所有数据都会被加载到驱动程序的内存中进行排序。 |
| saveAsTextFile(path) | 将 dataset 中的元素以文本文件的形式写入本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中。Spark 将对每个元素调用 toString 方法,将元素转换为文本文件中的一行记录。 |
| saveAsSequenceFile(path) | 将 dataset 中的元素以Hadoop SequenceFile 的形式写入到本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中。该操作要求RDD中的元素需要实现 Hadoop 的 Writable 接口。对于Scala语言而言,它可以将Spark中的基本数据类型自动隐式转换为对应Writable类型。(目前仅支持Java and Scala) |
| saveAsObjectFile(path) | 使用 Java 序列化后存储,可以使用 SparkContext.objectFile() 进行加载。(目前仅支持Java and Scala) |
| countByKey() | 计算每个键出现的次数。 |
| foreach(func) | 遍历RDD中每个元素,并对其执行fun函数 |
2.1 reduce
使用函数func执行归约操作:
val list = List(1, 2, 3, 4, 5)
sc.parallelize(list).reduce((x, y) => x + y)
sc.parallelize(list).reduce(_ + _)
// 输出 15
2.2 takeOrdered
按自然顺序(natural order)或自定义比较器(custom comparator)排序后返回前 n 个元素。需要注意的是takeOrdered使用隐式参数进行隐式转换,以下为其源码。所以在使用自定义排序时,需要继承Ordering[T]实现自定义比较器,然后将其作为隐式参数引入。
def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T] = withScope {
.........
}
自定义规则排序:
// 继承Ordering[T],实现自定义比较器,按照value值的长度进行排序
class CustomOrdering extends Ordering[(Int, String)] {
override def compare(x: (Int, String), y: (Int, String)): Int
= if (x._2.length > y._2.length) 1 else -1
}
val list = List((1, "hadoop"), (1, "storm"), (1, "azkaban"), (1, "hive"))
// 引入隐式默认值
implicit val implicitOrdering = new CustomOrdering
sc.parallelize(list).takeOrdered(5)
// 输出: Array((1,hive), (1,storm), (1,hadoop), (1,azkaban)
2.3 countByKey
计算每个键出现的次数:
val list = List(("hadoop", 10), ("hadoop", 10), ("storm", 3), ("storm", 3), ("azkaban", 1))
sc.parallelize(list).countByKey()
// 输出: Map(hadoop -> 2, storm -> 2, azkaban -> 1)
2.4 saveAsTextFile
将 dataset 中的元素以文本文件的形式写入本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中。Spark 将对每个元素调用 toString 方法,将元素转换为文本文件中的一行记录。
val list = List(("hadoop", 10), ("hadoop", 10), ("storm", 3), ("storm", 3), ("azkaban", 1))
sc.parallelize(list).saveAsTextFile("/usr/file/temp")
参考资料
更多大数据系列文章可以参见个人 GitHub 开源项目: 程序员大数据入门指南
Spark学习之路(四)—— RDD常用算子详解的更多相关文章
- Spark 系列(四)—— RDD常用算子详解
一.Transformation spark 常用的 Transformation 算子如下表: Transformation 算子 Meaning(含义) map(func) 对原 RDD 中每个元 ...
- Hadoop学习之路(8)Yarn资源调度系统详解
文章目录 1.Yarn介绍 2.Yarn架构 2.1 .ResourceManager 2.2 .ApplicationMaster 2.3 .NodeManager 2.4 .Container 2 ...
- Webwork 学习之路【04】Configuration 详解
Webwork做为经典的Web MVC 框架,个人觉得源码中配置文件这部分代码的实现十分考究. 支持自定义自己的配置文件.自定义配置文件读取类.自定义国际化支持. 可以作为参考,单独引入到其他项目中, ...
- Docker学习之路(二)DockerFile详解
Dockerfile是一个镜像的表示,可以通过Dockerfile来描述构建镜像的步骤,并自动构建一个容器 所有的 Dockerfile 命令格式都是: INSTRUCTION arguments 虽 ...
- lucene学习笔记(四)lucene分词详解
分词器的核心类 Analyzer SimpleAnalyzer StopAnalyzer WhitespaceAnalyzer StandardAnalyzer TokenStream 分词器做好处理 ...
- 性能测试学习之路 (二)jmeter详解(jmeter执行顺序 && 作用域 && 断言 && 事务 &&集合点 )
1 Jmeter 工作区介绍 jmeter工作区分为3个部分:目录树.测试计划编辑区域.菜单栏. 2 Jmeter 执行顺序规则 Jmeter执行顺序规则如下: 配置元件 前置处理器 定时器 采样器s ...
- ElasticSearch7.3学习(二十四)----相关度评分机制详解
1.算法介绍 relevance score(相关性分数) 算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度.Elasticsearch使用的是 term freque ...
- Spark:常用transformation及action,spark算子详解
常用transformation及action介绍,spark算子详解 一.常用transformation介绍 1.1 transformation操作实例 二.常用action介绍 2.1 act ...
- [转]Spark学习之路 (三)Spark之RDD
Spark学习之路 (三)Spark之RDD https://www.cnblogs.com/qingyunzong/p/8899715.html 目录 一.RDD的概述 1.1 什么是RDD? ...
随机推荐
- python 和为S的连续正数序列
题目描述: 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100.但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数).没多久, ...
- uva 11892 - ENimEN(推理)
题目链接:uva 11892 - ENimEN 题目大意:给定n堆石子的个数,两人轮流选择石子堆取石子,直到不能取为失败,附加条件,假设前一次操作,即队手的操作,没有将选中石子堆中的石子取完,那么当前 ...
- Windows下静态编译Qt4
既然是静态编译,那就要编译出来的程序不信赖于任何dll文件.首先下载qt-win-opensource-4.7.4-mingw.exe: http://get.qt.nokia.com/qt/sour ...
- Linux性能测试 strace命令
1 功能说明 strace 命令是一种强大的工具 , 能够显示任何由用户空间程式发出的系统调用 . strace 显示这些调用的参数并返回符号形式的值 . strace 从内核接收信息 , ...
- Chart.js报告
引进需要Chart.js <%@ page language="java" pageEncoding="UTF-8"%> <!DOCTYPE ...
- 用游戏杆控制WPF中三维模型
原文:用游戏杆控制WPF中三维模型 用游戏杆控制WPF中三维模型 今天心情比较好,不写WF的文章了,换个主题.写一个我最最最擅长的内容. 例子下载: http://files.cnblogs. ...
- python短信轰炸机版本smsbomb----------部分(post)
用一些用手机号注冊且须要发送验证码的站点的漏洞.能够向不论什么人的手机号发送短信,当然短信内容,我们无法控制.所以主要工作还是寻找这种站点.然后利用Fiddler或者HttpWatch分析请求.使用p ...
- the solution about "messy code" in elicpse
I use the Elicpse IDE to develope the ansdroid app.Sometime encounter the messy code in the Elicpse ...
- WPF特效-实现3D足球效果
原文:WPF特效-实现3D足球效果 WPF 实现 3D足球效果,效果图如下: 每个面加载不同贴图. <UserControl x:Class="MediaBalll.Model3Ds ...
- Java Socket 爬虫
# 地址 https://github.com/mofadeyunduo/crawler # 前言 1.代码不断优化更新. 2.有建议请留言. # 介绍 1.多线程,基于 ExcutorServcie ...