Spark 2.4 RDD操作API(Java语言描述)

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 + 1k * 2k * 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 = ...;

如果调用rdd0join方法,并将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
发布了117 篇原创文章 · 获赞 96 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/abc123lzf/article/details/103430902