常用SparkRDD容易混淆的算子区别(Scala版本)

常用SparkRDD容易混淆的算子区别

1.map与flatMap的区别
# 初始化数据
val rdd1 = sc.parallelize(Array("hello world","i love you"))
  • map
# map算子
rdd1.map(_.split(" ")).collect

# map算子结果输出
res0: Array[Array[String]] = Array(Array(hello, world), Array(i, love, you))
  • flatMap
# flatMap算子
rdd1.flatMap(_.split(" ")).collect

# flatMap算子结果输出
res1: Array[String] = Array(hello, world, i, love, you)

flatMap是将数据先进行map转化,在通过flatternmap结果进行’压平’。也就是将map转化的2个Array压平处理。

flatMap的效率要优于map


2.map与mapPartitions的区别

map是对rdd中的每一个元素进行操作

mapPartitions(foreachPartition)则是对rdd中的每个分区的迭代器进行操作

如果在map过程中需要频繁创建额外的对象(例如将rdd中的数据通过jdbc写入数据库,map需要为每个元素创建一个链接而mapPartition为每个partition创建一个链接),则mapPartitions效率比map高的多

  • map示例
# 初始化数据,设置3个分区
val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),3)


rdd.map(x=>{println(x);x*2}).collect

输出结果:

image

  • mapPartitions示例
rdd.mapPartitions(x=>{println("-----------");for(i<-x)yield i*2}).collect

输出结果:

image

通过对比,我们可以发现,mapPartitions在每个分区只会调用一次。而map每次都会被调用。所以在特定的场景mapPartitions效率比map高的多


3.mapPartitionsWithIndex示例
  • mapPartitionsWithIndex

类似于mapPartitions,但取两个参数。

第一个参数是分区的索引(index),第二个参数是通过这个分区中的所有项的迭代器(iterator)。

输出是一个迭代器,它包含在应用函数编码的任何转换之后的项目列表。

# 初始化数据
val rdd = sc.makeRDD(1 to 10, 3)

# mapPartitionsWithIndex方法是调用,传入一个函数,函数参数1为index,参数2为iterator
rdd.mapPartitionsWithIndex((index, iter) => iter.map((index, _))).foreach(println)

输出结果:

image


4.mapValues与flatMapValues
  • mapValues
# 初始化rdd
val rdd = sc.makeRDD(List("hello spark", "hello java", "hello python", "hello r","hello scala"), 2)


rdd.map(x => (x.length % 3, x)).mapValues(x => x.split(" ")).collect()

输出结果:

image

  • flatMapValues
# 初始化rdd
val rdd = sc.makeRDD(List("hello spark", "hello java", "hello python", "hello r","hello scala"), 2)

rdd.map(x => (x.length % 3, x)).flatMapValues(x => x.split(" ")).collect()

输出结果:

image

flatMapValues是把map中的value进行过函数操作后,再将数据结构压平。


5.coalesce、repartition与partitionBy
  • coalesece

该函数用于将RDD进行重分区,使用HashPartitioner进行分区。

接受2个参数,第一个参数为重分区的数目,第二个为是否进行shuffle,默认为false;

val rdd1 = sc.parallelize(1 to 12,3)
println(rdd1.partitions.length) //输出结果 3

val rdd2 = rdd1.coalesce(2)
println(rdd2.partitions.length) // 输出结果 2

//如果重分区的数目大于原来的分区数,那么必须指定shuffle参数为true,//否则,分区数不变
val rdd3 = rdd1.coalesce(4)
println(rdd3.partitions.length) // 输出结果 3

val rdd4 = rdd1.coalesce(4,true)
println(rdd4.partitions.length) // 输出结果 4

image

  • repartition

该函数其实就是coalesce函数第二个参数为true的实现

def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
    coalesce(numPartitions, shuffle = true)
  }

示例:

val rdd1 = sc.parallelize(1 to 12,3)

val rdd5 = rdd1.repartition(4)

println(rdd5.partitions.length) // 输出结果 4

image

  • partitionBy

partitionBy:表示重新分区,不是仅仅是设置分区数


var rdd1 = sc.makeRDD(Array((1, "A"), (2, "B"), (3, "C"), (4, "D")), 2)
rdd1.mapPartitionsWithIndex { (index, iter) => for (i <- iter) yield (index, i) }.foreach(println)

# 重新设置分区
var rdd2 = rdd1.partitionBy(new org.apache.spark.HashPartitioner(2))
rdd2.mapPartitionsWithIndex { (index, iter) => for (i <- iter) yield (index, i) }.foreach(println)

结果如图:4.13图

image


6.union、distinct、intersection、subtract
  • union

求并集

val rdd1 = sc.parallelize(List(5, 6, 4, 3))
val rdd2 = sc.parallelize(List(1, 2, 3, 4))
rdd1.union(rdd2).collect

# 输出结果,这个结果不会去重
res52: Array[Int] = Array(5, 6, 4, 3, 1, 2, 3, 4)
  • distinct

去重

val rdd1 = sc.parallelize(List(5, 6, 4, 3))
val rdd2 = sc.parallelize(List(1, 2, 3, 4))
val rdd3 = rdd1.union(rdd2)

rdd3.distinct.collect

# 输出结果,结果去重
res53: Array[Int] = Array(4, 6, 2, 1, 3, 5)
  • intersection

求交集

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

rdd1.intersection(rdd2).collect

# 输出结果:
res54: Array[Int] = Array(4, 3)
  • subtract

求差集

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

rdd1.subtract(rdd2).collect

# 输出结果
res55: Array[Int] = Array(5, 6)



rdd2.subtract(rdd1).collect

# 输出结果
res56: Array[Int] = Array(1, 2)
  • subtractByKey

subtractByKey和基本转换操作中的subtract类似,只不过这里是针对K的

val rdd1 = sc.makeRDD(Array(("A", "1"), ("B", "2"), ("B", "3"), ("C", "3")), 2)
val rdd2 = sc.makeRDD(Array(("A", "a"), ("C", "c"), ("D", "d")), 2)

rdd1.subtractByKey(rdd2).collect

# 输出结果:因为rdd2的key中有A,C,所以差集就是B
res57: Array[(String, String)] = Array((B,2), (B,3))


rdd2.subtractByKey(rdd1).collect

# 输出结果:
res58: Array[(String, String)] = Array((D,d))

7.groupByKey、reduceByKey、foldByKey、aggregateByKey
  • groupByKey
val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2), ("shuke", 1)))
val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5)))
//求并集
val rdd3 = rdd1 union rdd2

rdd3.groupByKey().collect

# 输出结果:
res89: Array[(String, Iterable[Int])] = Array((tom,CompactBuffer(1, 3)), (jerry,CompactBuffer(3, 2)), (shuke,CompactBuffer(1, 2)), (kitty,CompactBuffer(2, 5)))


rdd3.groupByKey().map(t=>(t._1,t._2.sum)).collect

# 输出结果:
res90: Array[(String, Int)] = Array((tom,4), (jerry,5), (shuke,3), (kitty,7))

image

  • reduceByKey
val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2), ("shuke", 1)))
val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5)))

val rdd3 = rdd1.union(rdd2)
//按key进行聚合
rdd3.reduceByKey(_ + _).collect

# 输出结果:
res91: Array[(String, Int)] = Array((tom,4), (jerry,5), (shuke,3), (kitty,7))

groupByKey和reduceByKey都可以通过key来进行聚合。不同的是2者,聚合的过程是不一样的。

reduceByKey现在map过程中先进行聚合,再到reduce端聚合,减少数据太大带来的压力,减小RPC过程中的传输压力。

groupByKey是直接在reduce端进行聚合的,所以效率比reduceByKey低。

推荐使用reduceByKey,因为效率比groupByKey高

关于groupByKey与reduceByKey的推荐文章:reduceByKey和groupByKey区别与用法

  • foldByKey

该函数用于RDD[K,V]根据K将V做折叠、合并处理,其中的参数zeroValue表示先根据映射函数将zeroValue应用于V,进行初始化V,再将映射函数应用于初始化后的V.

val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2), ("shuke", 1)))
val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5)))

val rdd3 = rdd1.union(rdd2)

rdd3.foldByKey(0)(_ + _).collect

# 输出结果:
res84: Array[(String, Int)] = Array((tom,4), (jerry,5), (shuke,3), (kitty,7))

foldByKey和reduceByKey的功能是相似的,都是在map端先进行聚合,再到reduce聚合。不同的是flodByKey需要传入一个参数。该参数是计算的初始值。

  • aggregateByKey

aggregateByKey函数对PairRDD中相同Key的值进行聚合操作,在聚合过程中同样使用了一个中立的初始值。

def aggregateByKey[U](zeroValue: U)(seqOp: (U, V) => U, combOp: (U, U) => U)(implicit arg0: ClassTag[U]): RDD[(K, U)]
def aggregateByKey[U](zeroValue: U, numPartitions: Int)(seqOp: (U, V) => U, combOp: (U, U) => U)(implicit arg0: ClassTag[U]): RDD[(K, U)]
def aggregateByKey[U](zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U, combOp: (U, U) => U)(implicit arg0: ClassTag[U]): RDD[(K, U)]

示例:

val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2), ("shuke", 1)))
val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5)))

val rdd3 = rdd1.union(rdd2)

# aggregateByKey(0)(math.max(_,_),_+_)
# (0):0是传入的初始值
# math.max(_,_):是一个函数,表示传入的数据与初始值比较,取最小值
# _+_:和reduceByKey中一样,表示`(x,y)=>x+y`这样的函数

rdd3.aggregateByKey(0)(math.max(_,_),_+_).collect

# 输出结果:
res93: Array[(String, Int)] = Array((tom,4), (jerry,5), (shuke,3), (kitty,7))

aggregateByKey会先在本节点内先聚合,然后再聚合所有节点的结果。


8.zip函数与zipPartitions函数
  • zip函数

zip函数用于将两个RDD组合成Key/Value形式的RDD

var rdd1 = sc.makeRDD(1 to 5, 2)
var rdd2 = sc.makeRDD(Seq("A", "B", "C", "D", "E"), 2)

rdd1.zip(rdd2).foreach(println)

# 输出结果:
(1,A)
(2,B)
(3,C)
(4,D)
(5,E)


rdd2.zip(rdd1).foreach(println)

# 输出结果:
(A,1)
(B,2)
(C,3)
(D,4)
(E,5)

这里需要注意的是:

1.如果两个RDD分区数不同,则抛出异常:Can’t zip RDDs with unequal numbers of partitions

2.如果两个RDD的元素个数不同,则抛出异常:Can only zip RDDs with same number of elements in each partition

  • zipPartitions函数

zipPartitions函数将多个RDD按照partition组合成为新的RDD。

该函数需要组合的RDD具有相同的分区数,但对于每个分区内的元素数量没有要求。

2个RDD进行zipPartitions操作

var rdd1 = sc.makeRDD(1 to 5, 2)
var rdd2 = sc.makeRDD(Seq("A", "B", "C", "D", "E"), 2)

rdd1.zipPartitions(rdd2) {
  (rdd1Iter, rdd2Iter) => {
    var result = List[String]()
    while (rdd1Iter.hasNext && rdd2Iter.hasNext) {
      result ::= (rdd1Iter.next() + "_" + rdd2Iter.next())
    }
    result.iterator
  }
}.foreach(println)


# 输出结果:
2_B
1_A
5_E
4_D
3_C

3个RDD进行zipPartitions操作

var rdd1 = sc.makeRDD(1 to 5, 2)
var rdd2 = sc.makeRDD(Seq("A", "B", "C", "D", "E"), 2)
var rdd3 = sc.makeRDD(Seq("a", "b", "c", "d", "e"), 2)
var rdd4 = rdd1.zipPartitions(rdd2, rdd3) {
  (rdd1Iter, rdd2Iter, rdd3Iter) =>
  {
    var result = List[String]()
    while (rdd1Iter.hasNext && rdd2Iter.hasNext && rdd3Iter.hasNext) {
      result ::= (rdd1Iter.next() + "_" + rdd2Iter.next() + "_" + rdd3Iter.next())
    }
    result.iterator
  }
}
rdd4.foreach(println)


# 输出结果:
2_B_b
1_A_a
5_E_e
4_D_d
3_C_c

image

9.zipWithIndex函数与zipWithUniqueId函数
  • zipWithIndex函数

该函数将RDD中的元素和这个元素在RDD中的ID(索引号)组合成键/值对。

va rdd2 = sc.makeRDD(Seq("A", "B", "R", "D", "F"), 2)
rdd2.zipWithIndex().foreach(println)

# 输出结果:
(A,0)
(B,1)
(R,2)
(D,3)
(F,4)
  • zipWithUniqueId函数

该函数将RDD中元素和一个唯一ID组合成键/值对,
该唯一ID生成算法如下:

每个分区中第一个元素的唯一ID值为:该分区索引号

每个分区中第N个元素的唯一ID值为:(前一个元素的唯一ID值) + (该RDD总的分区数)

var rdd1 = sc.makeRDD(Seq("A", "B", "C", "D", "E", "F"), 2)
rdd1.mapPartitionsWithIndex {
  (x, iter) => {
    var result = List[String]()
    while (iter.hasNext) {
      result ::= ("part_" + x + "|" + iter.next())
    }
    result.iterator

  }
}.foreach(println)
rdd1.zipWithUniqueId().foreach(println)

结果如图:

image

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java实现: ```java import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import scala.Tuple2; import java.util.ArrayList; import java.util.List; public class GlomExample { public static void main(String[] args) { SparkConf conf = new SparkConf().setAppName("GlomExample").setMaster("local"); JavaSparkContext sc = new JavaSparkContext(conf); List<Integer> data = new ArrayList<>(); for (int i = 1; i <= 10; i++) { data.add(i); } JavaRDD<Integer> rdd = sc.parallelize(data, 2); JavaRDD<List<Integer>> glomRdd = rdd.glom(); List<List<Integer>> result = glomRdd.collect(); for (int i = 0; i < result.size(); i++) { System.out.println("Partition " + i + ": " + result.get(i)); } sc.stop(); } } ``` Scala实现: ```scala import org.apache.spark.{SparkConf, SparkContext} object GlomExample { def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName("GlomExample").setMaster("local") val sc = new SparkContext(conf) val data = 1 to 10 val rdd = sc.parallelize(data, 2) val glomRdd = rdd.glom() val result = glomRdd.collect() for (i <- result.indices) { println(s"Partition $i: ${result(i).toList}") } sc.stop() } } ``` Python实现: ```python from pyspark import SparkConf, SparkContext conf = SparkConf().setAppName("GlomExample").setMaster("local") sc = SparkContext(conf=conf) data = range(1, 11) rdd = sc.parallelize(data, 2) glom_rdd = rdd.glom() result = glom_rdd.collect() for i in range(len(result)): print(f"Partition {i}: {list(result[i])}") sc.stop() ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值