字节二面:讲一下Spark的transformation算子吧!

每一次 Transformation 操作都会产生新的RDD,供给下一个“转换”使用;转换得到的RDD是惰性求值的。也就是说,整个转换过程只是记录了转换的轨迹, 并不会发生真正的计算,只有遇到 Action 操作时,才会发生真正的计算,开始从血 缘关系(lineage)源头开始,进行物理的转换操作。

一、transformation算子有哪些

Transformation

意义

map(func)

返回通过函数 func 传递源的每个元素而形成的新的分布式数据集。

filter(func)

返回通过选择 func 返回 true 的源元素而形成的新数据集。

flatMap(func)

与 map 类似,但每个输入项可以映射到 0 个或多个输出项(因此 func 应返回 Seq 而不是单个项)。

mapPartitions(func)

与 map 类似,但在 RDD 的每个分区(块)上单独运行,因此在 T 类型的 RDD 上运行时,func 的类型必须是迭代器<T>   => 迭代器<U>。

mapPartitionsWithIndex(func)

与 map分区类似,但也为 func 提供了一个表示分区索引的整数值,因此在 T 类型的 RDD 上运行时,func 的类型必须为(Int、迭代器<T>) = >迭代器<U>。

sample(withReplacement, fraction, seed)

使用给定的随机数生成器种子对数据的一小部分进行采样,无论是否使用替换。

union(otherDataset)

返回一个新数据集,其中包含源数据集中元素的并集和参数。

intersection(otherDataset)

返回一个新的 RDD,其中包含源数据集中元素的交集和参数。

distinct([numPartitions]))

返回包含源数据集的不同元素的新数据集。

groupByKey([numPartitions])

在 (K,V) 对的数据集上调用时,返回 (K,可迭代<V>)对的数据集。
  注意:如果要分组以对每个键执行聚合(如总和或平均值),则使用 或 将产生更好的性能。
  注意: 默认情况下,输出中的并行度级别取决于父 RDD 的分区数。您可以传递可选参数来设置不同数量的任务。reduceByKeyaggregateByKeynumPartitions

reduceByKey(func,   [numPartitions])

在 (K,V) 对的数据集上调用时,返回 (K, V) 对的数据集,其中每个键的值使用给定的 reduce 函数 func 聚合,该函数的类型必须为 (V,V) = > V。与 in 一样,reduce 任务的数量可以通过可选的第二个参数进行配置。groupByKey

aggregateByKey(zeroValue)(seqOp, combOp,   [numPartitions])

在 (K,V) 对的数据集上调用时,返回 (K, U) 对的数据集,其中每个键的值使用给定的组合函数和中性的“零”值进行聚合。允许使用与输入值类型不同的聚合值类型,同时避免不必要的分配。与 in 一样,reduce 任务的数量可以通过可选的第二个参数进行配置。groupByKey

sortByKey([ascending],   [numPartitions])

当在 K 实现 Order 的 (K, V) 对数据集上调用时,将返回 (K, V) 对的数据集,这些对按键按升序或降序排序,如布尔参数中所述。ascending

join(otherDataset,   [numPartitions])

当调用类型为 (K, V) 和 (K, W) 的数据集时,返回 (K, (V, W)) 对的数据集,其中包含每个键的所有元素对。通过 、 和 支持外连接。leftOuterJoinrightOuterJoinfullOuterJoin

cogroup(otherDataset,   [numPartitions])

当调用类型 (K, V) 和 (K, W) 的数据集时,返回 (K, (可迭代<V>、 可迭代<W>)) 元组的数据集。此操作也称为 。groupWith

cartesian(otherDataset)

在 T 和 U 类型的数据集上调用时,返回 (T, U) 对(所有元素对)的数据集。

pipe(command, [envVars])

通过外壳命令(例如 Perl 或 bash 脚本)对 RDD 的每个分区进行管道传输。RDD 元素将写入进程的 stdin,而输出到其标准输出的行将作为字符串的 RDD 返回。

coalesce(numPartitions)

将 RDD 中的分区数减少到数字分区数。对于在筛选大型数据集后更有效地运行操作非常有用。

repartition(numPartitions)

随机重新洗牌RDD中的数据以创建更多或更少的分区,并在它们之间平衡它。这总是会随机处理网络上的所有数据。

repartitionAndSortWithinPartitions(partitioner)

根据给定的分区程序对RDD进行重新分区,并在每个生成的分区中按键对记录进行排序。这比在每个分区中调用然后排序更有效,因为它可以将排序向下推到随机播放机制中。repartition

从上表知道了有哪些transformation算子,怎么使用呢?下面就带你研究!

二、Value类型怎么使用

1. map()映射

1) 功能说明:参数f是一个函数可以写作匿名子类,它可以接收一个参数。当某个RDD执行map方法时,会遍历该RDD中的每一个数据项,并依次应用f函数,从而产生一个新的RDD。即,这个新RDD中的每一个元素都是原来RDD中每一个元素依次应用f函数而得到的。

2)具体实现

package com.example.value;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

public class Test01_Map {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDlineRDD = sc.textFile("input/1.txt");

// 需求:每行结尾拼接||

// 两种写法 lambda表达式写法(匿名函数)

JavaRDDmapRDD = lineRDD.map(s -> s + "||");

// 匿名函数写法

JavaRDDmapRDD1 = lineRDD.map(new Function() {

@Override

public String call(String v1) throws Exception {

return v1 + "||";

}

});

for (String s : mapRDD.collect()) {

System.out.println(s);

}

// 输出数据的函数写法

mapRDD1.collect().forEach(a -> System.out.println(a));

mapRDD1.collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

 

2. flatMap()扁平化

1)功能说明:与map操作类似,将RDD中的每一个元素通过应用f函数依次转换为新的元素,并封装到RDD中。

区别:在flatMap操作中,f函数的返回值是一个集合,并且会将每一个该集合中的元素拆分出来放到新的RDD中。

2)需求说明:创建一个集合,集合里面存储的还是子集合,把所有子集合中数据取出放入到一个大的集合中。

3)具体实现​​​​​​​

package com.example.value;

import org.apache.commons.collections.ListUtils;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.FlatMapFunction;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Iterator;

import java.util.List;

public class Test02_FlatMap {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

ArrayList<list> arrayLists = new ArrayList<>();

arrayLists.add(Arrays.asList("1","2","3"));

arrayLists.add(Arrays.asList("4","5","6"));

JavaRDD<list> listJavaRDD = sc.parallelize(arrayLists,2);

// 对于集合嵌套的RDD 可以将元素打散

// 泛型为打散之后的元素类型

JavaRDDstringJavaRDD = listJavaRDD.flatMap(new FlatMapFunction<list, String>() {

@Override

public Iteratorcall(Liststrings) throws Exception {

return strings.iterator();

}

});

stringJavaRDD. collect().forEach(System.out::println);

// 通常情况下需要自己将元素转换为集合

JavaRDDlineRDD = sc.textFile("input/2.txt");

JavaRDDstringJavaRDD1 = lineRDD.flatMap(new FlatMapFunction() {

@Override

public Iteratorcall(String s) throws Exception {

String[] s1 = s.split(" ");

return Arrays.asList(s1).iterator();

}

});

stringJavaRDD1. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

3. groupBy()分组

1)功能说明:分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。

2)需求说明:创建一个RDD,按照元素模以2的值进行分组。

3)具体实现​​​​​​​

package com.example.value;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

import java.util.Arrays;

public class Test03_GroupBy {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDintegerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);

// 泛型为分组标记的类型

JavaPairRDD<integer, iterable> groupByRDD = integerJavaRDD.groupBy(new Function() {

@Override

public Integer call(Integer v1) throws Exception {

return v1 % 2;

}

});

groupByRDD.collect().forEach(System.out::println);

// 类型可以任意修改

JavaPairRDD<boolean, iterable> groupByRDD1 = integerJavaRDD.groupBy(new Function() {

@Override

public Boolean call(Integer v1) throws Exception {

return v1 % 2 == 0;

}

});

groupByRDD1. collect().forEach(System.out::println);

Thread.sleep(600000);

// 4. 关闭sc

sc.stop();

}

}

groupBy会存在shuffle过程。

shuffle将不同的分区数据进行打乱重组的过程。

shuffle一定会落盘。可以在local模式下执行程序,通过4040看效果。

4. filter()过滤

1)功能说明:接收一个返回值为布尔类型的函数作为参数。当某个RDD调用filter方法时,会对该RDD中每一个元素应用f函数,如果返回值类型为true,则该元素会被添加到新的RDD中。

2)需求说明:创建一个RDD,过滤出对2取余等于0的数据。

3)代码实现​​​​​​​

package com.example.value;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

import java.util.Arrays;

public class Test04_Filter {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDintegerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);

JavaRDDfilterRDD = integerJavaRDD.filter(new Function() {

@Override

public Boolean call(Integer v1) throws Exception {

return v1 % 2 == 0;

}

});

filterRDD. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

5. distinct()去重

1)功能说明:对内部的元素去重,并将去重后的元素放到新的RDD中。

2)代码实现​​​

package com.example.value;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import java.util.Arrays;

public class Test05_Distinct {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDintegerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6), 2);

// 底层使用分布式分组去重 所有速度比较慢,但是不会OOM

JavaRDDdistinct = integerJavaRDD.distinct();

distinct. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

注意:distinct会存在shuffle过程。

6. sortBy()排序

1)功能说明:该操作用于排序数据。在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理的结果进行排序,默认为正序排列。排序后新产生的RDD的分区数与原RDD的分区数一致。Spark的排序结果是全局有序。

2)需求说明:创建一个RDD,按照数字大小分别实现正序和倒序排序。

3)代码实现​​​​​​​

package com.example.value;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

import java.util.Arrays;

public class Test6_SortBy {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDintegerJavaRDD = sc.parallelize(Arrays.asList(5, 8, 1, 11, 20), 2);

// (1)泛型为以谁作为标准排序 (2) true为正序 (3) 排序之后的分区个数

JavaRDDsortByRDD = integerJavaRDD.sortBy(new Function() {

@Override

public Integer call(Integer v1) throws Exception {

return v1;

}

}, true, 2);

sortByRDD. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

三、Key-Value类型怎么使用

1)创建包名:com.example.keyvalue。

2)  要想使用Key-Value类型的算子首先需要使用特定的方法转换为PairRDD。​​​​​​​

package com.example.keyValue;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.PairFunction;

import scala.Tuple2;

import java.util.Arrays;

public class Test01_pairRDD{

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDintegerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);

JavaPairRDDpairRDD = integerJavaRDD.mapToPair(new PairFunction() {

@Override

public Tuple2call(Integer integer) throws Exception {

return new Tuple2<>(integer, integer);

}

});

pairRDD. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

1. mapValues()只对V进行操作

1)功能说明:针对于(K,V)形式的类型只对V进行操作。

2)需求说明:创建一个pairRDD,并将value添加字符串"|||"。

3)代码实现​​​​​​​

package com.example.keyValue;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

import scala.Tuple2;

import java.util.Arrays;

public class Test02_MapValues {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaPairRDDjavaPairRDD = sc.parallelizePairs(Arrays.asList(new Tuple2<>("k", "v"), new Tuple2<>("k1", "v1"), new Tuple2<>("k2", "v2")));

// 只修改value 不修改key

JavaPairRDDmapValuesRDD = javaPairRDD.mapValues(new Function() {

@Override

public String call(String v1) throws Exception {

return v1 + "|||";

}

});

mapValuesRDD. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

2. groupByKey()按照K重新分组

1)功能说明:groupByKey对每个key进行操作,但只生成一个seq,并不进行聚合。该操作可以指定分区器或者分区数(默认使用HashPartitioner)。

2)需求说明:统计单词出现次数。

3)代码实现:​​​​​​​

package com.example.keyValue;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

import org.apache.spark.api.java.function.PairFunction;

import scala.Tuple2;

import java.util.Arrays;

public class Test03_GroupByKey {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDintegerJavaRDD = sc.parallelize(Arrays.asList("hi","hi","hello","spark" ),2);

// 统计单词出现次数

JavaPairRDDpairRDD = integerJavaRDD.mapToPair(new PairFunction() {

@Override

public Tuple2call(String s) throws Exception {

return new Tuple2<>(s, 1);

}

});

// 聚合相同的key

JavaPairRDD<string, iterable> groupByKeyRDD = pairRDD.groupByKey();

// 合并值

JavaPairRDDresult = groupByKeyRDD.mapValues(new Function<iterable, Integer>() {

@Override

public Integer call(Iterablev1) throws Exception {

Integer sum = 0;

for (Integer integer : v1) {

sum += integer;

}

return sum;

}

});

result. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}}

3. reduceByKey()按照K聚合V

1)功能说明:该操作可以将RDD[K,V]中的元素按照相同的K对V进行聚合。其存在多种重载形式,还可以设置新RDD的分区数。

2)需求说明:统计单词出现次数。

3)代码实现:​​​​​​​

package com.example.keyValue;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

import org.apache.spark.api.java.function.Function2;

import org.apache.spark.api.java.function.PairFunction;

import scala.Tuple2;

import java.util.Arrays;

public class Test04_ReduceByKey {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaRDDintegerJavaRDD = sc.parallelize(Arrays.asList("hi","hi","hello","spark" ),2);

// 统计单词出现次数

JavaPairRDDpairRDD = integerJavaRDD.mapToPair(new PairFunction() {

@Override

public Tuple2call(String s) throws Exception {

return new Tuple2<>(s, 1);

}

});

// 聚合相同的key

JavaPairRDDresult = pairRDD.reduceByKey(new Function2() {

@Override

public Integer call(Integer v1, Integer v2) throws Exception {

return v1 + v2;

}

});

result. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

4. reduceByKey和groupByKey区别

1)reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[K,V]。

2)groupByKey:按照key进行分组,直接进行shuffle。

3)开发指导:在不影响业务逻辑的前提下,优先选用reduceByKey。求和操作不影响业务逻辑,求平均值影响业务逻辑。影响业务逻辑时建议先对数据类型进行转换再合并。​​​​​​​

package com.example.keyValue;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaRDD;

import org.apache.spark.api.java.JavaSparkContext;

import org.apache.spark.api.java.function.Function;

import org.apache.spark.api.java.function.Function2;

import org.apache.spark.api.java.function.PairFunction;

import scala.Tuple2;

import java.util.Arrays;

public class Test06_ReduceByKeyAvg {

public static void main(String[] args) throws InterruptedException {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaPairRDDjavaPairRDD = sc.parallelizePairs(Arrays.asList(new Tuple2<>("hi", 96), new Tuple2<>("hi", 97), new Tuple2<>("hello", 95), new Tuple2<>("hello", 195)));

// ("hi",(96,1))

JavaPairRDD<string, tuple2> tuple2JavaPairRDD = javaPairRDD.mapValues(new Function<integer, tuple2>() {

@Override

public Tuple2call(Integer v1) throws Exception {

return new Tuple2<>(v1, 1);

}

});

// 聚合RDD

JavaPairRDD<string, tuple2> reduceRDD = tuple2JavaPairRDD.reduceByKey(new Function2<tuple2, Tuple2, Tuple2>() {

@Override

public Tuple2call(Tuple2v1, Tuple2v2) throws Exception {

return new Tuple2<>(v1._1 + v2._1, v1._2 + v2._2);

}

});

// 相除

JavaPairRDDresult = reduceRDD.mapValues(new Function<tuple2, Double>() {

@Override

public Double call(Tuple2v1) throws Exception {

return (new Double(v1._1) / v1._2);

}

});

result. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

5. sortByKey()按照K进行排序​​​​​​​

package com.example.keyValue;

import org.apache.spark.SparkConf;

import org.apache.spark.api.java.JavaPairRDD;

import org.apache.spark.api.java.JavaSparkContext;

import scala.Tuple2;

import java.util.Arrays;

public class Test05_SortByKey {

public static void main(String[] args) {

// 1.创建配置对象

SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");

// 2. 创建sparkContext

JavaSparkContext sc = new JavaSparkContext(conf);

// 3. 编写代码

JavaPairRDDjavaPairRDD = sc.parallelizePairs(Arrays.asList(new Tuple2<>(4, "a"), new Tuple2<>(3, "c"), new Tuple2<>(2, "d")));

// 填写布尔类型选择正序倒序

JavaPairRDDpairRDD = javaPairRDD.sortByKey(false);

pairRDD. collect().forEach(System.out::println);

// 4. 关闭sc

sc.stop();

}

}

四、总结

1、Value数据类型的Transformation算子,这种变换不触发提交作业,针对处理的数据项是Value型的数据,从输入输出角度来看,大致分为三种:一对一型(map、flatMap、mapPartitions),多对一型(union、certesian),多对多型(groupBy)。  

2、Key-Value数据类型的Transformation算子,这种变换不触发提交作业,针对处理的数据项是Key-Value型的数据,从输入输出角度来看,大致分为三种:一对一(mapValues);聚集(combineByKey、reduceByKey、cogrou等);连接(join、leftOutJoin算子等) 。

在工作中Value类型和Key-Value类型我们都需要经常使用,也会经常混合使用以达到我们想要的效果。如果想要快速掌握这些算子,我们就需要去理解每个算子能对数据产生什么样的作用,想要快速感受到这些算子的妙用,这就需要我们多多练习了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值