Spark RDD常见的转化和行动操作

1基本RDD

1.1 针对各个元素的转化操作

map()、filter()

两个最常用的转化操作是map()和filter()。转化操作map()接收一个函数,把这个函数用于RDD中的每个元素,每个元素经函数的返回结果作为新RDD中对应元素的值。而转化操作filter()则接收一个函数,并将RDD中满足该函数的元素放入新RDD中返回。
在这里插入图片描述
例如,用map()对RDD中的所有数求平方:

val input = sc.parallelize(List(1,2,3,4))
val result = input.map(x => x * x)
println(result.collect().mkString(","))

flatMap()

有时候,我们希望对每个输入元素生成多个输出元素。实现该功能的操作叫flatMap()。和map()类似,我们提供给flatMap()的函数被分别应用到了输入RDD的每个元素上。不过返回的不是一个元素,而是一个返回值序列的迭代器。输出的RDD倒不是由迭代器组成的。我们得到的是一个包含各个迭代器可访问的所有元素的RDD。flatMap()的一个简单用途是把输入的字符串切分为单词,如下例所示:

val lines = sc.parallelize(List("hello world", "hi spark"))
val words = lines.flatMap(line => line.split(" "))
words.first() //return "hello"

如下图所示,可以把flatMap()看作将返回的迭代器“拍扁”,这样就得到了一个由各列表中的元素组成的RDD,而不是一个由列表组成的RDD
在这里插入图片描述

1.2 伪集合操作

distinct()

尽管RDD本身不是严格意义上的集合,但它也支持许多数学上的集合操作,比如合并和相交操作。RDD中常常有重复 的元素,如果只要唯一的元素,可以使用RDD.distinct()转化操作来生成一个只包含不同元素的新RDD。不过需要注意的是,distinct()操作的开销很大,因为它需要将所有数据通过网络进行混洗(shuffle),以确保每个元素都只有一份。
在这里插入图片描述
union()

最简单的集合操作是union(other),它会返回一个包含两个RDD中所有元素的RDD,并且如果输入的RDD中有重复数据,union()操作也会包含这些重复数据。

intersec()

Spark还提供了intersection(other)方法,只返回两个RDD中都有的元素,它会去掉所有重复的元素(单个RDD内的重复元素也会去重),intersection()也需要通过网络混洗数据来发现共有的元素。

subtract()

subtract(other)函数接收另一个RDD作为参数,返回一个由只存在于第一个RDD中而不存在于第二个RDD中的所有元素组成的RDD,它也需要数据混洗。

cartesian()

cartesian(other)计算两个RDD的笛卡尔积,会返回所有可能的(a, b)对,其中a是源RDD中的元素,而b则来自另一个RDD。不过要特别注意的是,求大规模RDD的笛卡尔积开销巨大。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

1.3 行动操作

reduce()

基本RDD上最常见的行动操作是reduce()。它接收一个函数作为参数,这个函数要操作RDD中两个元素类型的数据并返回一个同样类型的新元素,这个返回的新元素和RDD中的下一个同类型的元素再经函数操作再返回一个新元素,以此迭代下去,如可利用它对RDD进行累加:

val l = sc.parallelize(List(1,2,3,4))
l.reduce((x, y) => x + y)

对于这个x,它代指的是返回值,而y是对RDD各元素的遍历。意思是对sum中的数据进行累加。

fold()

fold()和reduce()类似,接收一个与reduce()接收的函数签名相同的函数,并加了一个初始值参数来作为每个分区第一次调用时的结果。

val l = sc.parallelize(List(1,2,3,4))
l.fold(0)((x, y) => x + y)

这个计算其实 0 + 1 + 2 + 3 + 4,而reduce()的计算是:1 + 2 + 3 + 4,没有初始值,或者说RDD的第一个元素值是它的初始值。

fold()和reduce()都要求函数的返回值类型需要和我们所操作的RDD中的元素类型相同。但有时我们确实需要返回一个不同类型的值。例如,在计算平均值时,需要记录遍历过程中的计数以及元素的数量,这就需要我们返回一个二元组。可以先对数据使用map()操作,来把元素转为该元素和1的二元组,这样reduce()就可以以二元组的形式进行归约了。

aggregate()

aggregate()函数可以返回与RDD中元素类型不相同的结果。与fold()类似,使用aggregate()时,需要提供所期待返回的类型的初始值。然后通过一个函数把RDD中的元素合并起来放入累加器。考虑到每个节点是在本地进行累加的,最终,还需要提供第二个函数来将累加器两两合并。

比如刚才说到的计算平均值,最终我要返回一个二元组(Int,Int),这个二元组的第一个元素是RDD中各元素的累加和,第二个元素是RDD中的元素个数,因此我设置初始值为(0,0),接着我设置第一个函数为:

(x, y) => (x._1 + y, x._2 + 1)

x表示返回值,第一次调用即是初始值(0,0),y就是遍历RDD中的元素,x._1 + y就是RDD中的元素累加,x._2 + 1就是RDD中的元素个数,遍历完成后得到的就是RDD的元素之和及元素个数(累加器)。但是因为我们的计算是分布式计算,每个计算节点都会得到一个累加器,所以需要第二个函数将累加器两两合并

例如第一个节点遍历1和2, 返回的是(3, 2),第二个节点遍历3和4, 返回的是(7, 2),那么将它们合并的话就是3 + 7, 2 + 2,第二个函数为:

(x, y) => (x._1 + y._1, x._2 + y._2)

最终利用aggregate()函数求平均值是这样的:

val l = sc.parallelize(List(1,2,3,4))
val r = l.aggregate(0, 0)(
		(x, y) => (x._1 + y, x._2 + 1), 
		(x, y) => (x._1 + y._1, x._2 + y._2))
val a = r._1 / r._2.toDouble

RDD的一些行动操作会以普通集合或者值的形式将RDD的部分或全部数据返回驱动器程序中。

collect()

把数据返回驱动器程序中最简单、最常见的操作是collect(),它会将整个RDD的内容返回。由于需要将数据复制到驱动器进程中,collect()要求所有数据都必须能一同放入单台机器的内存中。

take(n)

take(n)返回RDD中的n个元素,并且尝试只访问尽量少的分区,因此该操作会得到一个不均衡的集合。需要注意的是,这些操作返回元素的顺序与你预期的可能不一样。

top()

如果为数据定义了顺序,就可以使用top() 从RDD 中获取前几个元素。top() 会使用数据的默认顺序,但我们也可以提供自己的比较函数,来提取前几个元素。

takeSample(withReplacement, num,seed)

有时需要在驱动器程序中对我们的数据进行采样。takeSample(withReplacement, num,seed) 函数可以让我们从数据中获取一个采样,并指定是否替换。

有时我们会对RDD 中的所有元素应用一个行动操作,但是不把任何结果返回到驱动器程序中,这也是有用的。比如可以用JSON 格式把数据发送到一个网络服务器上,或者把数据存到数据库中。不论哪种情况,都可以使用foreach() 行动操作来对RDD 中的每个元素进行操作,而不需要把RDD 发回本地。
在这里插入图片描述

参考 《spark快速大数据分析》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RDD:弹性分布式数据集(ResilientDistributed Dataset),是Spark对数据的核心抽象。RDD其实是分布式的元素集合。当Spark对数据操作和转换时,会自动将RDD中的数据分发到集群,并将操作并行化执行。 Spark中的RDD是一个不可变的分布式对象集合。每个RDD都倍分为多个分区,这些分区运行在集群中的不同节点。RDD可以包含Python、Java、Scala中任意类型的对象,甚至可以包含用户自定义对象,本文主要通过Java实现相关示例。 Spark程序或shell会话工作流程 1. 从外部数据创建出输入RDD; 2. 使用诸如filter()等这样的转化操作RDD进行转化,以定义新的RDD; 3. 告诉Spark对需要被重用的中间结果RDD执行persist()操作; 4. 使用诸如first()等这样的行动操作来触发一次并行计算,Spark会对计算进行优化后再执行。 一. 创建RDD Spark提供了两种创建RDD方式: 1. 读取外部数据集,如文件,hive数据库等; 2. 在驱动器程序中对一个集合进行并行化,如list,set等。 方法1是常用方法,其从外部存储中读取数据来创建RDD,如读取文件 方法1创建RDD 方法2其实使用较少,毕竟它需要把整个数据集先放在一台机器的内存中。实现也简单,就是把程序中一个已有集合传给SparkContext的parallelize()方法。二.RDD操作 方法2创建RDD 二. RDD操作 1. RDD支持两种操作: (1) 转化操作RDD转化操作是返回一个新的RDD操作,比如map()和filter。 (2) 行动操作RDD行动操作则是向驱动器程序返回结果或把结果写入外部系统的操作,会触发实际的计算,比如count()和first()。 惰性求值:RDD转化操作是惰性求值的,即在被调用行动操作之前Spark不会开始计算,相反,Spark会在内部记录下索要求执行的操作的相关信息。例如,当我们调用jsc.textFile()时,数据并没有读取进来,而是在必要时才会读取。Spark使用惰性求值,就可以把一些操作合并到一起来减少计算数据的步骤。 2. RDD的基本转化操作 函数名 目的 示例 结果 map() 将函数应用于RDD的每一元素, 将返回值构成新的RDD rdd.map(x=>x+1) {2,3,4,4} flatMap() 将函数应用于RDD的每一元素, 将返回的迭代器的所有内容构成新的RDD. 通常用于切分单词 rdd.flatMap(x=>x.to(3)) {1,2,3,2,3,3,3} filter() 返回一个由通过传给filter()的函数 的元素组成的RDD rdd.filter(x=>x!=1) {2,3,3} distinct() 去重 rdd.distinct() {1,2,3) sample(withReplacement, fraction,[seed]) 对RDD采用,以及是否替换 rdd.sample(false,0.5) 非确定的 对一个数据为{1,2,3,3}的RDD进行基本的RDD转化操作 函数名 目的 示例 结果 union() 生成一个包含两个RDD 中所有元素的RDD rdd.union(other) {1, 2, 3, 3, 4, 5} intersection() 求两个RDD 共同的元素的RDD rdd.intersection(other) {3} subtract() 移除一个RDD 中的内容(例如移除训练数据) rdd.subtract(other) {1, 2} cartesian() 与另一个RDD 的笛卡儿积 rdd.cartesian(other) {(1, 3), (1, 4), ...(3, 5)} 对数据分别为{1, 2,3}和{3, 4, 5}的RDD进行针对两个RDD转化操作 3. RDD的基本执行操作 函数名 目的 示例 结果 collect() 返回RDD 中的所有元素 rdd.collect() {1, 2, 3, 3} count() RDD 中的元素个数 rdd.count() 4 countByValue() 各元素在RDD 中出现的次数 rdd.countByValue() {(1, 1),(2, 1),(3, 2)} take(num) 从RDD 中返回num 个元素 rdd.take(2) {1, 2} top(num) 从RDD 中返回最前面的num个元素 rdd.top(2) {3, 3} takeOrdered(num) (ordering) 从RDD 中按照提供的顺序返回最前面的num 个元素 rdd.takeOrdered(2)(myOrdering) {3, 3} takeSample(withReplacement, num, [seed]) 从RDD 中返回任意一些元素 rdd.takeSample(false, 1) 非确定的 reduce(func) 并行整合RDD 中所有数据(例如sum) rdd.reduce((x, y) => x + y) 9 fold(zero)(func) 和reduce() 一样, 但是需要提供初始值 注意:不重复元素加初始值,重复元素只加一个 rdd.fold(0)((x, y) => x + y) 9 aggregate(zeroValue) (seqOp, combOp) 和reduce() 相似, 但是通常返回不同类型的函数 注意:不重复元素加初始值,重复元素只加一个 rdd.aggregate((0, 0))((x, y) => (x._1 + y, x._2 + 1),(x, y) => (x._1 + y._1, x._2 + y._2)) (9,4) foreach(func) 对RDD 中的每个元素使用给定的函数 rdd.foreach(func) 无 对一个数据为{1, 2,3, 3}的RDD进行基本的RDD行动操作 4. 标准Java函数接口 在Java中,函数需要作为实现了Spark的org.apache,spark.api.java.function包中的任一函数接口的对象传递。 函数名 实现的方法 用途 Function<T, R> R call(T) 接收一个输入值并返回一个输出值,用于类似map() 和filter() 等操作中 Function2<T1, T2, R> R call(T1, T2) 接收两个输入值并返回一个输出值,用于类似aggregate()和fold() 等操作中 FlatMapFunction<T, R> Iterable<R> call(T) 接收一个输入值并返回任意个输出,用于类似flatMap()这样的操作中 标准Java函数接口 5. Java中针对专门类型的函数接口 函数名 等价函数 用途 DoubleFlatMapFunction<T> Function<T, Iterable<Double>> 用于flatMapToDouble,以生成DoubleRDD DoubleFunction<T> Function<T, Double> 用于mapToDouble,以生成DoubleRDD PairFlatMapFunction<T, K, V> Function<T, Iterable<Tuple2<K, V>>> 用于flatMapToPair,以生成PairRDD<K, V> PairFunction<T, K, V> Function<T, Tuple2<K, V>> 用于mapToPair, 以生成PairRDD<K, V> Java中针对专门类型的函数接口 三. 示例 本节将通过示例的方式验证第二节中相关的转化操作行动操作转化行动计算结果 代码地址: 参考文献: 王道远 《Spark 快速大数据分析》

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值