Spark RDD Operations
RDD支持两种操作:转换(transformation)从现有的数据集创建一个新的数据集;而动作(actions)在数据集上运行计算后,返回一个值给驱动程序。 例如,map就是一种转换,它将数据集每一个元素都传递给函数,并返回一个新的分布数据集表示结果。另一方面,reduce是一种动作,通过一些函数将所有的元素叠加起来,并将最终结果返回给Driver程序。(不过还有一个并行的reduceByKey,能返回一个分布式数据集)
Spark中的所有转换都是惰性的,也就是说,他们并不会直接计算结果。相反的,它们只是记住应用到基础数据集(例如一个文件)上的这些转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这个设计让Spark更加有效率的运行。例如,我们可以实现:通过map创建的一个新数据集,并在reduce中使用,最终只返回reduce的结果给driver,而不是整个大的新数据集。
默认情况下,每一个转换过的RDD都会在你在它之上执行一个动作时被重新计算。不过,你也可以使用persist(或者cache)方法,持久化一个RDD在内存中。在这种情况下,Spark将会在集群中,保存相关元素,下次你查询这个RDD时,它将能更快速访问。在磁盘上持久化数据集,或在集群间复制数据集也是支持的,这些选项将在本文档的下一节进行描述。
下面的表格列出了目前所支持的转换和动作(详情请参见 RDD API doc):
map(func) | 返回一个新分布式数据集,由每一个输入元素经过func函数转换后组成 |
filter(func) | 返回一个新数据集,由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(因此func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分块上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithSplit(func) | 类似于mapPartitions, 但func带有一个整数参数表示分块的索引值。因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U] |
sample(withReplacement,fraction, seed) | 根据fraction指定的比例,对数据进行采样,可以选择是否用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) | 返回一个新的数据集,新数据集是由源数据集和参数数据集联合而成 |
distinct([numTasks])) | 返回一个包含源数据集中所有不重复元素的新数据集 |
groupByKey([numTasks]) | 在一个(K,V)对的数据集上调用,返回一个(K,Seq[V])对的数据集 注意:默认情况下,只有8个并行任务来做操作,但是你可以传入一个可选的numTasks参数来改变它 |
reduceByKey(func, [numTasks]) | 在一个(K,V)对的数据集上调用时,返回一个(K,V)对的数据集,使用指定的reduce函数,将相同key的值聚合到一起。类似groupByKey,reduce任务个数是可以通过第二个可选参数来配置的 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)对的数据集上调用,K必须实现Ordered接口,返回一个按照Key进行排序的(K,V)对数据集。升序或降序由ascending布尔参数决定 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)类型的数据集上调用时,返回一个相同key对应的所有元素对在一起的(K, (V, W))数据集 |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的数据集上调用,返回一个 (K, Seq[V], Seq[W])元组的数据集。这个操作也可以称之为groupwith |
cartesian(otherDataset) | 笛卡尔积,在类型为 T 和 U 类型的数据集上调用时,返回一个 (T, U)对数据集(两两的元素对) |
动作(actions)
reduce(func) | 通过函数func(接受两个参数,返回一个参数)聚集数据集中的所有元素。这个功能必须可交换且可关联的,从而可以正确的被并行执行。 |
collect() | 在驱动程序中,以数组的形式,返回数据集的所有元素。这通常会在使用filter或者其它操作并返回一个足够小的数据子集后再使用会比较有用。 |
count() | 返回数据集的元素的个数。 |
first() | 返回数据集的第一个元素(类似于take(1)) |
take(n) | 返回一个由数据集的前n个元素组成的数组。注意,这个操作目前并非并行执行,而是由驱动程序计算所有的元素 |
takeSample(withReplacement,num, seed) | 返回一个数组,在数据集中随机采样num个元素组成,可以选择是否用随机数替换不足的部分,Seed用于指定的随机数生成器种子 |
saveAsTextFile(path) | 将数据集的元素,以textfile的形式,保存到本地文件系统,HDFS或者任何其它hadoop支持的文件系统。对于每个元素,Spark将会调用toString方法,将它转换为文件中的文本行 |
saveAsSequenceFile(path) | 将数据集的元素,以Hadoop sequencefile的格式,保存到指定的目录下,本地系统,HDFS或者任何其它hadoop支持的文件系统。这个只限于由key-value对组成,并实现了Hadoop的Writable接口,或者隐式的可以转换为Writable的RDD。(Spark包括了基本类型的转换,例如Int,Double,String,等等) |
countByKey() | 对(K,V)类型的RDD有效,返回一个(K,Int)对的Map,表示每一个key对应的元素个数 |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新。这通常用于边缘效果,例如更新一个累加器,或者和外部存储系统进行交互,例如HBase |
# 创建一个变量data,类型为Array[Int]
scala> var data= Array(1, 2, 3, 4)
data: Array[Int] = Array(1, 2, 3, 4)
# 将data转化为RDD
scala> vardistData = sc.parallelize(data)
distData: org.apache.spark.rdd.RDD[Int] =ParallelCollectionRDD[23] at parallelize at <console>:14
# 应用map把distData的每个元素的值加1
# 其中collect是action方法返回数据集的所有元素
scala>distData.map(x => x + 1).collect()
…
14/07/10 15:40:28 INFO scheduler.DAGScheduler: Stage 1 (collectat <console>:17) finished in 0.006 s
14/07/10 15:40:28 INFO spark.SparkContext: Job finished: collectat <console>:17, took 0.017348 s
res12: Array[Int] = Array(2, 3, 4, 5)
# 应用filter把distData中元素值为1的过滤出来
scala>distData.filter(x => x == 1).collect()
…
14/07/10 15:45:28 INFO spark.SparkContext: Job finished: collectat <console>:17, took 0.011363 s
res20: Array[Int] = Array(1)
# 应用flatMap把distData的所有元素做扁平化扩展
scala> distData.flatMap(x => Array(x, 5, 6, 7)).collect()
…
14/07/10 15:48:57 INFO spark.SparkContext: Job finished: collectat <console>:17, took 0.013091 s
res22: Array[Int] = Array(1, 5, 6, 7, 2, 5, 6, 7, 3, 5, 6, 7, 4,5, 6, 7)
# mapPartitions(func)
还没搞定。。。
# mapPartitionsWithIndex(func)
同没搞定。。。
# sample按比例采样,结果比较奇怪
scala>distData.sample(false, 0.5, 0).collect()
…
res26: Array[Int] = Array(2)
scala>distData.sample(false, 1, 0).collect()
…
res27: Array[Int] = Array(1, 2, 3, 4)
scala>distData.sample(false, 0.9, 0).collect()
…
res28: Array[Int] = Array(1, 2, 3)
scala>distData.sample(false, 0.5, 0).collect()
…
res29: Array[Int] = Array(2)
scala>distData.sample(true, 0.5, 0).collect()
…
res30: Array[Int] = Array(1, 4)
scala>distData.sample(true, 0.5, 1).collect()
…
res31: Array[Int] = Array(1, 3, 3, 3, 3, 3, 4)
scala>distData.sample(false, 2, 0).collect()
…
res1: Array[Int] = Array(1, 2, 3, 4)
# 用union合并两个RDD,结果显示没有去重,仅是简单的合并
scala> valtmpData = sc.parallelize(Array(3, 4, 5, 6))
scala>distData.union(tmpData).collect()
…
res3: Array[Int] = Array(1, 2, 3, 4, 3, 4, 5, 6)
# intersection,竟然没有这个方法,我用的0.8的spark
# 好吧,回头搞个1.0的spark再试
scala>distData.intersection(tmpData).collect()
<console>:19: error: value intersection is not a member oforg.apache.spark.rdd.RDD[Int]
distData.intersection(tmpData).collect()
# 应该返回res3: Array[Int] = Array(3, 4)才对嘛!
# 用distinct返回去重后的元素
scala>distData.map(x => x % 2).distinct().collect()
…
res9: Array[Int] = Array(0, 1)
# groupByKey
scala>distData.map(x => (x%2, x)).collect()
…
res18: Array[(Int, Int)] = Array((1,1), (0,2), (1,3), (0,4))
scala>distData.map(x => (x%2, x)).groupByKey().collect()
…
res15: Array[(Int, Seq[Int])] = Array((0,ArrayBuffer(2, 4)),(1,ArrayBuffer(1, 3)))
# reduceByKey
scala>distData.map(x => (x%2, x)).reduceByKey(_ + _).collect()
…
res17: Array[(Int, Int)] = Array((0,6), (1,4))
# sortByKey 按key降序排序,value顺序不变
scala> distData.map(x => (x%2, x)).sortByKey(false).collect()
…
res21: Array[(Int, Int)] = Array((1,1), (1,3), (0,2), (0,4))
# join,只join了key相同的部分,对应自然连接
scala>distData.map(x => (x, x)).join(tmpData.map(x => (x, x))).collect()
…
res22: Array[(Int, (Int, Int))] = Array((3,(3,3)), (4,(4,4)))
# cogroup 相当于全连接,从结果可以看到和join的区别
scala>distData.map(x => (x, x)).cogroup(tmpData.map(x => (x, x))).collect()
…
res23: Array[(Int, (Seq[Int], Seq[Int]))] =Array((1,(ArrayBuffer(1),ArrayBuffer())), (2,(ArrayBuffer(2),ArrayBuffer())),(3,(ArrayBuffer(3),ArrayBuffer(3))), (4,(ArrayBuffer(4),ArrayBuffer(4))),(5,(ArrayBuffer(),ArrayBuffer(5))), (6,(ArrayBuffer(),ArrayBuffer(6))))
# cartesian 返回两个集合的笛卡尔集
scala>distData.cartesian(tmpData).collect()
…
res24: Array[(Int, Int)] = Array((1,3), (1,4), (1,5), (1,6),(2,3), (2,4), (2,5), (2,6), (3,3), (3,4), (3,5), (3,6), (4,3), (4,4), (4,5),(4,6))
# reduce 返回所有元素的和
scala>distData.reduce(_ + _)
…
res32: Int = 10
# count
scala>distData.count()
…
res33: Long = 4
# first
scala>distData.first()
…
res34: Int = 1
# take,num大于数据个数时返回全部数据
scala>distData.take(2)
…
res35: Array[Int] = Array(1, 2)
scala>distData.take(5)
…
res36: Array[Int] = Array(1, 2, 3, 4)
# countByKey
scala>distData.map(x => (x%2, x)).countByKey()
…
res40: scala.collection.Map[Int,Long] = Map(1 -> 2, 0 ->2)
# takeSample
scala> distData.takeSample(false, 2, 0)
…
res38: Array[Int] = Array(4, 3)
# foreach
scala> val accum= sc.accumulator(0)
accum: org.apache.spark.Accumulator[Int] = 0
scala>distData.foreach(x => accum += x)
…
14/07/10 13:40:18 INFO scheduler.DAGScheduler: Stage 24 (foreachat <console>:19) finished in 0.012 s
14/07/10 13:40:18 INFO spark.SparkContext: Job finished: foreachat <console>:19, took 0.033934 s
scala>accum.value
res36: Int = 10