Spark RDD数据操作函数以及转换函数一文详解运用与方法

本文详细介绍了如何在Scala环境下使用Spark进行RDD的创建、转换和操作,包括map、sortBy、collect、flatMap、filter、distinct、union、intersection和subtract等,以及键值对RDD和各种高级函数如reduceByKey、groupByKey、join和combineByKey的应用。

前言:

配置JDK1.8

实验环境IDEA

scala版本为2.11.12

本地Window伪分布运行非集群实验

创建RDD

从内存中创建一个RDD有两种常用的方法。一种是转化Seq集合为RDD,另一种是从已有RDD转化为新的RDD。

SparkContext类中有两个方法:parallelize和makeRDD。

1.parallelize

parallelize有两个参数可以输入

(1)要转化的集合,必须是Seq集合。

(2)分区数,一般不设分区数,则默认为该Application分配到的资源的CPU数。

val rdd1 = sc.parallelize(List(1,2,3,4))

2.makeRDD

makeRDD有两种实现方法:一种跟parallelize完全一致;另一种接收的参数类型是Seq,生产的RDD中保存的是T的值(Seq[T,Seq[String]))。

val seq = Seq((1,Seq(1,2)),(2,Seq(2,3,4)))
val rdd =sc.makeRDD(seq)
rdd.collect().foreach(println(_))                     

 (1,List(1, 2))
(2,List(2, 3, 4))

从外部存储创建RDD是指直接读取一个存放在文件系统的数据文件创建RDD,第一种创建RDD的方式常用于测试,这种方式才是用于实践操作的常用方法。

(1)从HDFS文件创建RDD

val test = sc.textFile("/user/root/test.txt")

(2)从Linux本地文件创建

确实差不多,在路径前面加上file://表示从本地Linux文件系统读取。

1.Map转换数据

map是一种基础的RDD转换操作,用于将RDD中的每一个数据元素通过某种函数进行转换并返回新的RDD。

例:

val distData = List(1, 3, 45, 3, 76)
val sq_dist = distData.map(x => x * x)
print(sq_dist)

List(1, 9, 2025, 9, 5776)

2.SortBy()排序

sortBy()是对标准RDD进行排序的方法,在org.apache.spark.rdd.RDD类中实现:

/**

 * Return this RDD sorted by the given key function.

 */

def sortBy[K](

    f: (T) => K,

    ascending: Boolean = true,

    numPartitions: Int = this.partitions.size)

    (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] =

  this.keyBy[K](f)

      .sortByKey(ascending, numPartitions)

      .values

  • 第一个参数是函数f(x)=>_._._,左边是要被排序对象中的每一个元素,右边返回的值是元素中要进行排序的值。
  • 第二个参数是ascending排序顺序,决定排序后RDD中的元素是升序还是降序,默认是ture
  • 第三个参数是numPartitions,该参数决定排序后的RDD分区个数,默认排序后的分区个数和排序之前的个数相等。

例:

val data = List((5,3),(888,666),(777,65))
val sort_data=data.sortBy(x=>x._1)
print(sort_data)

List((5,3), (777,65), (888,666))

3.collect()查询

 collect函数是一个行动操作,把RDD所有元素转换成数组并返回到Driver端,适用于小数据处理后的返回。

sq_data.collect

Array[(Int,Int)] = Array((7,6),(45,3),(1,3))

4.flatMap转换数据

faltMap的操作是将函数应用于RDD之中的每一个元素,将返回的迭代器中的所有元素构成新的RDD。

简单的来讲,使用faltmap就是先map然后flat迭代输出:

val test = List("How are you", "I am fine", "What about you")
print(test.flatMap(x => x.split(" ")))

List(How, are, you, I, am, fine, What, about, you)

5.take()查询指定数目的值

take(N)方法用于获取RDD的前N个元素,返回类型为数组。take与collect的原理相似,collect用于获取全部数据,take获取指定个数的数据。

val data = sc.parallelize(1 to 10)
data.take(5)

Array[Int] = Array(1,2,3,4,5)

6.union()合并多个RDD

union是一种转换操作,用于将两个RDD的元素合并成一个,不进行去重操作,而且两个RDD中每个元素中的值的个数和类型需要保持一直。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3)))
val rdd2 = sc.parallelize(List(('a',1),('d',4),('e',5)))
rdd1.union(rdd2).collect

((a,1),(b,2),(c,3),(a,1),(d,4),(e,5))

7.filter()进行过滤

filter是一种转换操作,用于过滤RDD中的元素。filter需要一个参数,参数是一个用于过滤的函数,该函数的返回值为Boolean类型,返回值为true的元素保留,返回值为false的元素过滤,最后结果是返回一个存储符合过滤条件的所有元素的新RDD。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3)))
rdd1.filter(_._2>1).collect.foreach(println(_))

(b,2)
(c,3)

8.distinct()进行去重

distinct()是一个转换操作,用于RDD的数据去重,去除两个完全相同的元素,没有参数。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3),('a',1)))
rdd1.distinct().collect().foreach(println(_))

(b,2)
(c,3)
(a,1)

9.intersection()求出两个RDD的共同元素

intersection()方法用于求出两个RDD的共同元素,也就是找出两个RDD的交集,参数是另一个RDD,顺序先后与结果无关。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3),('a',1)))
val rdd2 = sc.parallelize(List(('a',1),('d',4),('e',5)))
rdd1.intersection(rdd2).collect().foreach(println(_))

(a,1)

10.subtract()将相同元素去掉

subtract()的参数是一个RDD,用于将前一个RDD中在后一个RDD出现的元素删除,可以看作是求补集的操作,返回值为前一个RDD去除与后一个RDD相同的元素后的剩余值所组成的新的RDD,所以RDD的顺序会影响结果。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3)))
val rdd2 = sc.parallelize(List(('a',1),('d',4),('e',5),('b',2)))
rdd1.subtract(rdd2).collect().foreach(println(_))
rdd2.subtract(rdd1).collect().foreach(println(_))

(c,3)
(e,5)
(d,4)

11.cartesian()求两个RDD的笛卡尔积

笛卡尔积就是将两个集合的元素两两组合成一组,假设集合A有5个元素,集合B有10个元素,集合A的每个元素都会和集合B的每个元素组合成一组,结果会返回50个元素组合。

val rdd1 = sc.parallelize(List(1,2,3,4))
val rdd2 = sc.parallelize(List(1,2,3))
rdd1.cartesian(rdd2).collect().foreach(println(_))

(1,1)
(1,2)
(1,3)
(2,1)
(2,2)
(2,3)
(3,1)
(3,2)
(3,3)
(4,1)
(4,2)
(4,3)

键值对RDD

键值对RDD由一组组的键值对组成,这些RDD被称为PairRDD。PairRDD提供了并行操作各个键或跨节点重新进行数据分组的操作接口。

val rdd= sc.parallelize(List("this is a test","hellow world ","come on "))
val words = rdd.map(x=>(x.split(" ")(0),x));
words.collect().foreach(println(_))

(this,this is a test)
(hellow,hellow world )
(come,come on )

转换操作Keys与Values

作为键值对类型的RDD,包含了键和值两部分。Spark提供了两种方法,分别获取键值对RDD的键和值。keys返回一个仅包含键的RDD,values返回了一个仅包含值的RDD。

val rdd= sc.parallelize(List("this is a test","hellow world ","come on "))
val words = rdd.map(x=>(x.split(" ")(0),x));
val key = words.keys
val value = words.values
key.collect().foreach(println(_))
value.collect().foreach(println(_))

this
hellow
come
this is a test
hellow world 
come on 
 

1.转换操作reduceByKey()

reduceByKey()的功能是合并具有相同键的值,作用域是Key/Value类型的键值对,并且是只对每个Key的Value进行处理,当RDD中有许多个键相同的键值对,那么就会对这个Key的Values进行处理。

val rdd1 = sc.parallelize(List(('a',1),('d',4),('e',5),('b',2),('a',1),('b',2),('c',3)))
val r_rdd=rdd1.reduceByKey((a,b)=>a+b)
r_rdd.collect().foreach(println(_))

(d,4)
(e,5)
(a,2)
(b,4)
(c,3)

2.转换操作groupByKey()

groupByKey()是对具有相同键的值进行分组,对于一个由类型K的键和类型V的值组成的RDD,通过groupByKey()得到的RDD类型是[K,Iterable[V]]。

val rdd1 = sc.parallelize(List(('a',1),('a',4),('b',5),('b',2),('a',1),('b',2),('c',3)))
val r_rdd=rdd1.groupByKey()
r_rdd.collect().foreach(println(_))
r_rdd.map(x=>(x._1,x._2.size)).collect().foreach(println(_))
//size()用于在指定的映射中查找键/值对的数量。

(a,CompactBuffer(1, 4, 1))
(b,CompactBuffer(5, 2, 2))
(c,CompactBuffer(3))
(a,3)
(b,3)
(c,1)

3.join()连接两个RDD

连接方式(对于学过数据库SQL的人来说比较容易理解):

连接类型描述
join对两个RDD进行内连接
rightOuterJoin对两个RDD进行连接操作,确保第二个RDD的键必须存在(右外连接)
leftOuterJoin对两个RDD进行连接操作,确保第一个RDD的键必须存在(左外连接)
fullOuterJoin对两个RDD进行全外连接

(1)join

join是根据键对两个RDD进行内连接,将两个RDD中键相同的数据的值存在一个元组中,最后只返回两个RDD都存在的键的连接结果。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3)))
val rdd2 = sc.parallelize(List(('a',1),('d',4),('e',5)))
val j_rdd = rdd1.join(rdd2)
j_rdd.collect().foreach(println(_))

(a,(1,1))

(2)rightOuterJoin

rightOuterJoin是根据键对两个RDD进行右外连接,连接结果返回第二个RDD的所有键的连接结果,不管在第一个RDD中是否存在。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3)))
val rdd2 = sc.parallelize(List(('a',1),('d',4),('e',5)))
val r_rdd = rdd1 rightOuterJoin rdd2
r_rdd.collect().foreach(println(_))

(d,(None,4))
(e,(None,5))
(a,(Some(1),1))

(3)leftOuterJoin

leftOuterJoin是对两个RDD的键进行左外连接的方法,与rightOuterJoin相反。返回结果保留第一个RDD的所有键。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3)))
val rdd2 = sc.parallelize(List(('a',1),('d',4),('e',5)))
val l_rdd = rdd1 leftOuterJoin rdd2
l_rdd.collect().foreach(println(_))

(a,(1,Some(1)))
(b,(2,None))
(c,(3,None))

(4)fullOuterJoin

fullOuterJoin是一种全外连接,会保留两个连接的RDD中所有键的连接结果。

val rdd1 = sc.parallelize(List(('a',1),('b',2),('c',3)))
val rdd2 = sc.parallelize(List(('a',1),('d',4),('e',5)))
val f_rdd = rdd1 fullOuterJoin rdd2
f_rdd.collect().foreach(println(_))

(d,(None,Some(4)))
(e,(None,Some(5)))
(a,(Some(1),Some(1)))
(b,(Some(2),None))
(c,(Some(3),None))

4.zip组合两个RDD

zip函数用于将两个RDD组合成Key/Value形式的RDD,这里要求两个RDD的partition数量以及元素数量都相同,否则会抛出异常。

val rdd1 = sc.parallelize(List(1,2,3,4,5))
val rdd2 = sc.parallelize(List('a','c','e','d','w'))
rdd1.zip(rdd2).collect().foreach(println(_))
rdd2.zip(rdd1).collect().foreach(println(_))

(1,a)
(2,c)
(3,e)
(4,d)
(5,w)
(a,1)
(c,2)
(e,3)
(d,4)
(w,5)
 

5.combineByKey合并相同键的值

combineByKey是Spark中一个比较核心的高级函数,其他一些高阶键值对函数底层都是用它来实现的。

combineByKey用于将相同键的数据聚合,并且允许返回类型与输入数据类型不同的返回值,combineByKey函数的定义为:

 def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
                /*content*/
  }

以上三个重要的参数:

(1)createCombiner:V=>C,V是键值对RDD中的值部分,将该值转换为另一种类型C,C会作为每一个键的累加器的初始值。

(2)mergeValue: (C, V) => C,该函数把元素V合并到之前的元素C(createCombiner)上(这个操作在每个分区进行)。

(3)mergeCombiners:(C, C)=>C,该函数把两个元素C合并(这个操作在不同分区间进行)。

由于聚合操作会遍历分区中所有的元素,因此每个元素的键只有两种情况:以前没出现过或以前出现过。

(1)如果以前没出现过,则执行的是createCombiner方法,createCombiner()会在新遇到的键对应的累加器中赋予初始值,否则执行mergeValue方法。

(2)对于已经出现过的键,调用mergeValue来进行聚合操作,对该键的累加器对应的当前值(C个数)与新值(V格式)进行合并。

(3)由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器,就需要使用用户提供的mergeCombiners()方法将各个分区的结果进行合并。


本文主要参考Spark大数据技术与运用一书。

### 回答1: Spark中的RDD(Resilient Distributed Datasets)是一种分布式的数据结构,它可以被分割成多个分区,每个分区可以在不同的节点上进行计算。RDD提供了一系列的转换操作函数,可以对RDD进行各种操作RDD转换函数包括map、filter、flatMap、union、distinct、groupByKey、reduceByKey、sortByKey等。这些函数可以对RDD进行转换操作,生成新的RDDRDD操作函数包括count、collect、reduce、take、foreach等。这些函数可以对RDD进行操作,返回结果或者将结果输出到外部系统。 在使用RDD时,需要注意一些问题,如RDD的惰性计算、RDD的持久化、RDD的分区等。同时,还需要根据实际情况选择合适的RDD转换操作函数,以达到最优的计算效果。 总之,Spark中的RDD转换操作函数是非常重要的,掌握它们可以帮助我们更好地使用Spark进行分布式计算。 ### 回答2: Spark是一个基于内存计算的分布式计算框架,可以实现大规模数据集的快速处理。在Spark中,RDD(弹性分布式数据集)是数据处理的核心概念,它是一种可以分区、并行计算和容错的不可变数据结构。而Spark中的函数式编程模型则将RDD转换操作都看做是函数的调用,从而简洁明了,易于理解和操作。 在Spark中,解决一个具体问题通常涉及一系列RDD转换操作RDD转换包括对原有RDD进行过滤、映射、聚合等处理,得到新的RDD操作则是对新的RDD进行输出、保存、统计、排序等操作。以下介绍几种常见的RDD转换操作函数。 1. map函数 map函数是一种转换函数,它可以将一个RDD中每个元素通过一个用户定义的函数映射到另一个RDD中,并得到新的RDD。例如,将一个整型RDD中的每个元素都乘以2后得到一个新的整型RDD: ``` val rdd1 = sc.parallelize(Array(1, 2, 3, 4)) val rdd2 = rdd1.map(x => x*2) ``` 2. filter函数 filter函数也是一种转换函数,它可以根据用户定义的条件过滤一个RDD中的元素,并得到一个新的RDD。例如,将一个字符串RDD中长度大于5的元素过滤出来得到一个新的字符串RDD: ``` val rdd1 = sc.parallelize(Array("hello", "world", "spark", "rdd")) val rdd2 = rdd1.filter(x => x.length > 5) ``` 3. reduce函数 reduce函数是一种操作函数,它可以将一个RDD中的元素按照用户定义的函数进行聚合并得到一个结果。例如,将一个整型RDD中的所有元素相加得到一个整数结果: ``` val rdd1 = sc.parallelize(Array(1, 2, 3, 4)) val result = rdd1.reduce((x, y) => x + y) ``` 4. collect函数 collect函数也是一种操作函数,它可以将一个RDD中的所有元素收集起来并输出到Driver端。然而,使用collect函数需要注意RDD的大小,如果RDD很大,就可能会出现内存溢出的情况。例如,将一个整型RDD中的所有元素收集起来并输出到屏幕: ``` val rdd1 = sc.parallelize(Array(1, 2, 3, 4)) val result = rdd1.collect() result.foreach(println) ``` 5. saveAsTextFile函数 saveAsTextFile函数也是一种操作函数,它可以将一个RDD中的所有元素保存到指定的文本文件中。例如,将一个字符串RDD中的所有元素保存到hdfs的一个文本文件中: ``` val rdd1 = sc.parallelize(Array("hello", "world", "spark", "rdd")) rdd1.saveAsTextFile("hdfs://localhost:8020/user/abc/output") ``` 总之,Spark中的RDD转换操作函数具有弹性、高效、简单等特点,能够满足各种大规模数据处理需求。需要特别注意的是,Spark中的函数式编程模型是基于JVM的,因此要充分利用内存和CPU资源,需要对集群配置和调优进行一定的优化和测试。 ### 回答3: Spark中的RDD(Resilient Distributed Datasets)是分布式的弹性数据集,它可以在大规模集群上并行化地计算,并且提供了一系列的转换操作函数。其中,Spark提供的Spark函数简单易用,具有高效的数据处理能力,可以帮助开发者快速开发分布式应用程序。 RDD转换函数是将一个RDD转换成另一个RDD函数转换后的RDD通常包含了数据处理、筛选和过滤后的新数据集,可以用来接着进行后续的计算。 例如,map函数可以将RDD中的每个元素应用一个函数,然后返回一个新的转换过的RDD: ``` val originalData = sc.parallelize(List(1, 2, 3, 4, 5)) val mappedData = originalData.map(x => x * 2) ``` 这里,map函数将原始数据中的每个元素都乘上了2,返回了一个新的RDD。 除了map函数, 还有flatMap、filter、groupBy等常用的转换函数,都可以帮助我们对RDD做出各种各样的数据处理和转换RDD操作函数则是对RDD进行真正的计算操作,例如reduce、count、collect等函数,这些函数会触发Spark分布式计算引擎执行真正的计算任务。 比如,reduce函数可以将RDD中的所有元素进行聚合,返回一个单一的结果: ``` val originalData = sc.parallelize(List(1, 2, 3, 4, 5)) val reducedData = originalData.reduce(_ + _) ``` 这里,reduce函数将原始数据中的所有元素进行相加操作,返回了一个整数类型的结果。 Spark提供的操作函数非常丰富,从基本的聚合、排序、统计操作,到高级的机器学习和图形处理等操作,开发者可以根据不同的业务需求灵活选择使用。 总之,Spark中的RDD转换操作函数分布式数据处理的核心之一,通过这些函数,开发者能够方便地对海量数据进行分布式的计算和处理。同时,Spark也提供了丰富的API和工具,便于开发者进行高效的Spark应用程序开发。
评论 12
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fanstuck

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值