Map
Map的作用就是将RDD
中的值逐一转换为另外一个值,例如下面将一个数组[1,2,3,4,5]
中每个元素都计算平方并返回:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD<Integer> rdd = ctx.parallelize(list);
rdd = rdd.map(k -> k * k);
rdd.collect().forEach(System.out::println);
ctx.stop();
}
}
map操作传入的函数也可以返回其它类型,例如将元素k转换为字符串类型,那么最终返回结果的类型为JavaRDD<String>
。
输出:
1
4
9
16
25
FlatMap
flatMap操作与Map操作类似,不过flatMap可以将一个值转换为多个值,例如下面对一个数组[1,2,3,4,5]
的每个元素分别计算出其k + 1
、k * 2
、k * k
的值:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD<Integer> rdd = ctx.parallelize(list);
rdd = rdd.flatMap(k -> Arrays.asList(k + 1, k * 2, k * k).iterator());
rdd.collect().forEach(System.out::println);
ctx.stop();
}
}
flatMap传入的FlatMapFunction
需要返回一个迭代器,其元素类型和map操作一样,可以自定义。
输出结果:
2
2
1
3
4
4
4
6
9
5
8
16
6
10
25
MapPartitions
MapPartitions操作产生的效果和FlatMap类似。不过MapPartitions不像Map、FlatMap那样逐一对每个元素进行操作,而是对一个JavaRDD
的每个分区中的元素进行统一操作。例如:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD<Integer> rdd = ctx.parallelize(list, 2); //注意这里分成了两个区
rdd = rdd.mapPartitions(it -> {
List<Integer> l = new ArrayList<>();
it.forEachRemaining(k -> l.add(k * k));
return l.iterator();
});
rdd.collect().forEach(System.out::println);
ctx.stop();
}
}
输出结果:
1
4
9
16
25
MapPartitionsWithIndex
MapPartitionsWithIndex在MapPartitions基础上添加了分区编号的获取功能。在上述示例中,我们将数组[1,2,3,4,5]
分成了两个区,但是我们是无法获知分区的具体编号。如果需要在函数中获知分区编号,那么需要使用到MapPartitionsWithIndex:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD<Integer> rdd = ctx.parallelize(list, 2);
JavaRDD<String> ans = rdd.mapPartitionsWithIndex((idx, it) -> {
List<String> l = new ArrayList<>();
it.forEachRemaining(k -> l.add("<" + idx + ":" + k + ">"));
return l.iterator();
}, false);
ans.collect().forEach(System.out::println);
ctx.stop();
}
}
其中mapPartitionsWithIndex
的第二个参数为是否保存分区。
输出结果:
<0:1>
<0:2>
<1:3>
<1:4>
<1:5>
Distinct
Distinct操作可以去除JavaRDD
重复的元素(equals
方法返回true
的元素),例如:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 5, 4);
JavaRDD<Integer> rdd = ctx.parallelize(list);
rdd = rdd.distinct();
rdd.collect().forEach(System.out::println);
ctx.close();
}
}
输出结果:
4
1
3
5
2
SortBy
SortBy操作可以将JavaRDD
中的元素进行排序,对于Java对象而言,要求该元素必须是可比较(实现Comparable
接口)。
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(5,3,2,1,4);
JavaRDD<Integer> rdd = ctx.parallelize(list, 2);
rdd = rdd.sortBy(k -> k, false, 2);
rdd.collect().forEach(System.out::println);
ctx.stop();
}
}
第一个参数需要传入一个函数,该函数负责对元素逐一进行额外的处理,可以返回其它类型,相当于Map的功能。第二个参数表示是否是升序序列,第三个参数为排序分区的数量。
TakeOrdered
TakeOrdered会对元素进行排序,然后取出排在前面指定数量的元素。例如:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(5,3,2,1,4);
JavaRDD<Integer> rdd = ctx.parallelize(list, 2);
rdd.takeOrdered(3).forEach(System.out::println);
ctx.stop();
}
}
输出结果:
1
2
3
此外takeOrdered
还有另外一个重载的方法,可以传入指定的Comparator
比较器。
TakeSample
TakeSample操作用于对JavaRDD
中的元素进行随机取样,例如
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Integer> list = Arrays.asList(5,3,2,1,4);
JavaRDD<Integer> rdd = ctx.parallelize(list, 2);
rdd.takeSample(false, 2).forEach(System.out::println);
ctx.stop();
}
}
上述操作会随机取出rdd
中的2个元素,所以输出结果不确定。
Cartesian
Cartesian操作用于对两个JavaRDD
对象(也可以是自身)生成笛卡尔积。例如:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
JavaRDD<Integer> rdd0 = ctx.parallelize(Arrays.asList(1, 2, 3));
JavaRDD<Character> rdd1 = ctx.parallelize(Arrays.asList('A', 'B', 'C', 'D'));
JavaPairRDD<Integer, Character> rdd = rdd0.cartesian(rdd1);
rdd.collect().forEach(System.out::println);
ctx.close();
}
}
上述操作对集合[1,2,3]
和[A,B,C,D]
计算其笛卡儿积,其输出结果为:
(1,A)
(1,B)
(1,C)
(1,D)
(2,A)
(2,B)
(2,C)
(2,D)
(3,A)
(3,B)
(3,C)
(3,D)
Join
Join操作具体来说就是将两个PairRDD
中_1
的交集合并到一个PairRDD
中。例如有两个PairRDD
:
JavaPairRDD<Integer, Integer> rdd0 = ...;
JavaPairRDD<Integer, String> rdd1 = ...;
如果调用rdd0
的join
方法,并将rdd1
作为参数传入:
JavaPairRDD<Integer, Tuple2<Integer, String>> rdd = rdd0.<String>join(rdd1);
那么会将rdd1
中与rdd0
相同的键整合到一个Tuple2
中。
示例代码:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("SparkDemo").setMaster("local[2]");
JavaSparkContext ctx = new JavaSparkContext(conf);
Map<Integer, Integer> map0 = new HashMap<>();
map0.put(1, 1);
map0.put(2, 2);
map0.put(3, 3);
map0.put(4, 4);
map0.put(5, 5);
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "A");
map1.put(2, "B");
map1.put(3, "C");
map1.put(4, "D");
map1.put(5, "E");
List<Tuple2<Integer, Integer>> list0 = new ArrayList<>();
map0.forEach((k, v) -> list0.add(new Tuple2<>(k, v)));
List<Tuple2<Integer, String>> list1 = new ArrayList<>();
map1.forEach((k, v) -> list1.add(new Tuple2<>(k, v)));
JavaPairRDD<Integer, Integer> rdd0 = ctx.<Integer, Integer>parallelizePairs(list0);
JavaPairRDD<Integer, String> rdd1 = ctx.<Integer, String>parallelizePairs(list1);
JavaPairRDD<Integer, Tuple2<Integer, String>> rdd = rdd0.<String>join(rdd1);
JavaRDD<String> ans = rdd.map((tuple) -> {
int k = tuple._1;
int v0 = tuple._2._1;
String v1 = tuple._2._2;
return String.format("<%d,%s>", k, v0 + ":" + v1);
});
List<String> list = ans.collect();
list.forEach(System.out::println);
ctx.stop();
}
}
运行结果:
<4,4:D>
<2,2:B>
<1,1:A>
<3,3:C>
<5,5:E>
Cogroup
Cogroup操作类似于SQL中的Full Outer Join
,相当于结合了左连接和右连接的查询结果:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Cogroup的处理对象是两个JavaPairRDD
,元组的第一个元素相当于上述SQL语句中的id
。
下面给出Cogroup的示例代码:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
Map<Integer, String> courseMap = new HashMap<>(); //4个课程
courseMap.put(1, "Java");
courseMap.put(2, "C/C++");
courseMap.put(3, "Computer Network");
courseMap.put(4, "Compilers Principles");
List<Tuple2<Integer, Integer>> scoreList = new ArrayList<>(); //6份成绩
addPair(scoreList, 1, 100);
addPair(scoreList, 1, 88);
addPair(scoreList, 1, 95);
addPair(scoreList, 2, 80);
addPair(scoreList, 2, 75);
addPair(scoreList, 3, 60);
JavaPairRDD<Integer, String> rdd0 = parseToRDD(ctx, courseMap);
JavaPairRDD<Integer, Integer> rdd1 = ctx.parallelizePairs(scoreList);
JavaPairRDD<Integer, Tuple2<Iterable<String>, Iterable<Integer>>> rdd = rdd0.cogroup(rdd1);
rdd.collect().forEach(tup -> {
int key = tup._1;
String course = tup._2._1.iterator().next(); //由于courseMap中key没有重复的,所以必然只有1个元素
List<Integer> score = new ArrayList<>();
tup._2._2.forEach(score::add);
System.out.println(String.format("课程ID:%d 名称:%s 成绩:%s", key, course, score));
});
ctx.close();
}
private static <T1, T2> JavaPairRDD<T1, T2> parseToRDD(JavaSparkContext ctx, Map<T1, T2> map) {
List<Tuple2<T1, T2>> list = new ArrayList<>();
map.forEach((k, v) -> list.add(new Tuple2<>(k, v)));
return ctx.parallelizePairs(list);
}
private static <T1, T2> void addPair(List<Tuple2<T1, T2>> list, T1 t1, T2 t2) {
list.add(new Tuple2<>(t1, t2));
}
}
上述代码中rdd0
相当于课程表,保存了课程ID和课程名的映射关系。rdd1
相当于成绩表,保存了课程ID和课程成绩的映射关系。然后我们可以通过Cogroup汇总出各个课程的成绩表。
输出结果:
课程ID:4 名称:Compilers Principles 成绩:[]
课程ID:1 名称:Java 成绩:[100, 88, 95]
课程ID:3 名称:Computer Network 成绩:[60]
课程ID:2 名称:C/C++ 成绩:[80, 75]
GroupByKey
GroupByKey操作的对象为JavaPairRDD
,作用是将Key(元组第一个元素)相同的元组中的Value(元组第二个元素)汇总到一起。例如:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Tuple2<Integer, Integer>> scoreList = new ArrayList<>();
addPair(scoreList, 1, 100);
addPair(scoreList, 1, 88);
addPair(scoreList, 1, 95);
addPair(scoreList, 2, 80);
addPair(scoreList, 2, 75);
addPair(scoreList, 3, 60);
JavaPairRDD<Integer, Integer> rdd = ctx.parallelizePairs(scoreList);
JavaPairRDD<Integer, Iterable<Integer>> ans = rdd.groupByKey();
ans.collect().forEach(tup -> {
int k = tup._1;
StringBuilder sb = new StringBuilder("[");
tup._2.forEach(sc -> sb.append(sc).append(','));
sb.append(']');
System.out.format("course:%d score:%s\n", k, sb);
});
ctx.close();
}
private static <T1, T2> void addPair(List<Tuple2<T1, T2>> list, T1 t1, T2 t2) {
list.add(new Tuple2<>(t1, t2));
}
}
输出结果:
course:1 score:[100,88,95,]
course:3 score:[60,]
course:2 score:[80,75,]
CountByKey
CountByKey的操作对象是JavaPairRDD
,主要是归纳出每个Key包含的相同的元组的数量,例如:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Tuple2<Integer, Integer>> scoreList = new ArrayList<>();
addPair(scoreList, 1, 100);
addPair(scoreList, 1, 88);
addPair(scoreList, 1, 95);
addPair(scoreList, 2, 80);
addPair(scoreList, 2, 75);
addPair(scoreList, 3, 60);
JavaPairRDD<Integer, Integer> rdd = ctx.parallelizePairs(scoreList);
rdd.countByKey().forEach((k, cnt) -> System.out.println("(" + k + "," + cnt + ")"));
ctx.close();
}
private static <T1, T2> void addPair(List<Tuple2<T1, T2>> list, T1 t1, T2 t2) {
list.add(new Tuple2<>(t1, t2));
}
}
输出结果:
(1,3)
(3,1)
(2,2)
其含义是key为1的元组有3个,key为2的元组有2个,key为3的元组有1个。
Reduce
Reduce操作负责对JavaRDD
或者JavaPairRDD
逐一进行计算,过程是将RDD中元素两两传递给传入的函数,经过函数计算后产生一个中间运算结果,然后将这个中间运算结果与RDD中下一个元素再次传递给该函数,直到RDD的最后一个元素。
例如下面代码负责计算数组元素的总和:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
JavaRDD<Integer> rdd = ctx.parallelize(Arrays.asList(1,2,3,4,5,6), 2);
int val = rdd.reduce(Math::addExact);
System.out.println(val);
ctx.close();
}
}
输出结果:
21
Aggregate
Aggregate操作首先对每个分区进行聚合计算,然后对每个分区的结果再和传入的初始值(zeroValue
)进行聚合计算。并可以更改聚合结果的类型。
例如下面的代码将1~10的集合分为2个区,然后对每个区的元素求最大值后再计算最大值的总和:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
JavaRDD<Integer> rdd = ctx.parallelize(Arrays.asList(1,2,3,4,5,6,7,8,9,10), 2);
int val = rdd.aggregate(0, Math::max, Math::addExact);
System.out.println(val);
ctx.close();
}
}
输出结果:
15
集合分为两个区后,分别为[1,2,3,4,5]
和[6,7,8,9,10]
,这两个区的最大值分别为5和10,然后对其求和,其结果为15。
AggregateByKey
AggregateKey操作针对JavaPairRDD
进行聚合计算。首先将JavaPairRDD
的各个分区中具有相同Key(元组第一个元素)的Value提取出来进行聚合运算进行独立计算,然后对各个分区的计算结果再进行聚合计算。例如:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
List<Tuple2<Integer, Integer>> scoreList = new ArrayList<>();
addPair(scoreList, 1, 100);
addPair(scoreList, 1, 88);
addPair(scoreList, 1, 95);
addPair(scoreList, 2, 80);
addPair(scoreList, 2, 75);
addPair(scoreList, 3, 60);
JavaPairRDD<Integer, Integer> rdd = ctx.parallelizePairs(scoreList, 3);
rdd = rdd.aggregateByKey(0, Math::max, Math::addExact);
rdd.collect().forEach(System.out::println);
ctx.close();
}
private static <T1, T2> void addPair(List<Tuple2<T1, T2>> list, T1 t1, T2 t2) {
list.add(new Tuple2<>(t1, t2));
}
}
输出结果为:
(3,60)
(1,195)
(2,155)
首先scoreList
被分成了三个区:[(1,100), (1,88)]
、[(1,95), (2,80)]
、[(2,75), (3,60)]
。接着对每个Key相同的元组进行最大值运算,运算完成后各个分区的结果为[(1,100)]
、[(1,95), (2,80)]
、[(2,75), (3,60)]
,然后汇总结果,并对每个Key相同的元组进行加法计算,最后就得出(1,195)
、(2,155)
、(3,60)
。
Fold
Fold操作是Aggregate的简化,即对Aggregate的两个步骤全部采用一致的函数,例如计算数组元素的和:
public class SparkDemo {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setAppName("SparkTest").setMaster("local");
JavaSparkContext ctx = new JavaSparkContext(conf);
JavaRDD<Integer> rdd = ctx.parallelize(Arrays.asList(1,2,3,4,5,6,7,8,9,10), 2);
int val = rdd.fold(0, Math::addExact);
System.out.println(val);
ctx.close();
}
}
输出结果:
55