Spark每日半小时(4)——Spark基础参数传值

向Spark传递参数

Spark的大部分转化操作和一部分行动操作,都需要依赖用户传递的函数来计算。在我们支持的三种主要语言中,向Spark传递函数的方式略有区别。这里主要写Java

在Java中,函数需要作为实现了Spark的org.apache.spark.api.java.function包中的任一函数接口的对象来传递。根据不同的返回来行,我们定义了一些不懂的接口。我们把最基本的一些函数接口列在下表中,同时介绍了一些其他的函数接口,在需要返回特殊类型(比如键值对)的数据时使用。

标准Java函数接口
函数名实现方法用途
Function<T,R>R call(T)接收一个输入值并返回一个输出值,用于类似map()和filter()等操作中
Function2<T1,T2,R>R call(T1,T2)接收两个输入值并返回一个输出值,用于类似aggregate()和fold()等操作中
FlatMapFunction<T,R>Iterable<R> call(T)接收一个输入值并返回任意个输出,用于类似flatMap()这样的操作

可以把我们的函数类内联定义为使用匿名内部类,也可以创建一个具名类

在Java中使用匿名内部类进行函数传递

        JavaRDD<String> errorLog = log.filter(
                (Function<String, Boolean>) s -> s.contains("error")
        );

在Java中使用具名类进行函数传递

        class ContainsError implements Function<String, Boolean> {
            @Override
            public Boolean call(String s) throws Exception {
                return s.contains("error");
            }
        }
        JavaRDD<String> errorLog = log.filter(new ContainsError());

集体风格的选择取决于个人偏好。不过我们发现顶级具名类通常在组织大型程序时显得比较清晰。使用顶级函数的另一个好处在于你可以给他们的构造函数添加参数。

带参数的Java函数类

        class ContainsError implements Function<String, Boolean> {
            private String query;

            public ContainsError(String query) {
                this.query = query;
            }

            @Override
            public Boolean call(String s) throws Exception {
                return s.contains(query);
            }
        }
        JavaRDD<String> errorLog = log.filter(new ContainsError("error"));

在Java8中,也可以使用Lambda表达式来简洁地实现函数接口。当然啦 ,我之前写的都是这样一种简介表达式。这里不再赘述。

常见地转化操作和行动操作

基本RDD

针对各个元素地转化操作

你很可能会用到的两个最常用的转化操作式map()和filter()。转化操作map()接收一个函数,把这个函数用于RDD中的每个元素,将函数的返回结果作为结果RDD中对应元素的值。而转化操作filter()则接收一个函数,并将RDD中满足该函数的元素放入新的RDD中返回。

我们可以使用map()来做各种各样的事情:可以把我们的URL集合中的每个URL对应的主机名提取出来,也可以简单到只对各个数字求平方值。map()的返回值类型不需要和输入类型一样。这样如果有一个字符串RDD,并且我们的map()函数是用来把字符串解析并返回一个Double值的,那么此时我们的输入RDD类型就是RDD[String],而输出类型RDD[Double]。

计算RDD中各值的平方

        JavaSparkContext sc = new JavaSparkContext(conf);
        JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(1, 2, 3, 4));
        JavaRDD<Integer> result = rdd.map(
                (Function<Integer, Integer>) integer -> integer * integer
        );
        System.out.println(StringUtils.join(result.collect(), ","));

有时候,我们希望对每个输入元素生成多个输出元素。实现该功能的操作叫做flatMap()。和map()类似,我们提供给flatMap()的函数被分别应用到了输入RDD的每个元素上。不过返回的不是一个元素,而是一个返回值序列的迭代器。输出的RDD倒不是由迭代器组成的。我们得到的是一个包含各个迭代器可访问的所有元素的RDD。flatMap()的一个简单用途是把输入的字符串切分为单词。

        JavaRDD<String> lines = sc.parallelize(Arrays.asList("hello world", "hi"));
        JavaRDD<String> words = lines.flatMap(
                (FlatMapFunction<String, String>) s
                        -> (Iterator<String>) Arrays.asList(s.split(" "))
        );
        System.out.println(words.first());

下图阐释了flatMap()和map()的区别。你可以把flatMap()看作将返回的迭代器”拍扁“,这样就得到了一个由各列表中的元素组成的RDD,而不是一个由列表组成的RDD。

伪集合操作

尽管RDD本身不是严格意义上的集合,但它也支持许多数学上的集合操作,比如合并和相交操作,下图展示了四种操作。注意,这些操作都要求操作的RDD是相同数据类型。

我们的RDD中最常确实的集合属性是元素的唯一性,因为常常有重复的元素。如果要唯一各元素,我们可以使用RDD.distinct()转化操作来生成一个只包含不同元素的新RDD。不过需要注意,distinct()操作的开销很大,因为它需要将所有数据通过网络进行混洗(shuffle),以确保每个元素都只有一份。

最简单的集合操作时union(other),它会返回一个包含两个RDD中所有元素的RDD。这在很多用例下都是很有用,比如处理来自多个数据源的日志文件。与数学中的union()操作不同的是,如果输入的RDD中有重复数据,Spark的union()操作也会包含这些重复数据。

Spark还提供了intersection(other)方法,只返回两个RDD中都有的元素。intersection()在运行时也会去掉所有重复的元素。尽管intersection()与union()的概念相似,intersection()的性能却要差很多,因为它需要通过网络混洗数据来发现共有的元素。

有时我们需要移除一些数据。subtract(other)函数接收另一个RDD作为参数,返回一个由只存在于第一个RDD中而不存在于第二个RDD中的所有元素组成的RDD。和intersection()一样,它也需要数据混洗。

我们也可以计算两个RDD的笛卡尔积。cartesian(other)转化操作会返回所有可能的(a,b)对,其中a是源RDD中的元素,而b则来自另一个RDD。笛卡尔积在我们希望考虑所有可能的组合的相似度时比较有用,比如计算各自用户对各种产品的预期兴趣程度。我们也可以求一个RDD与其自身的笛卡尔积,这可以用于求用户相似度的应用中。不过要特别注意的是,求大规模RDD的笛卡尔积开销巨大。

对一个数据为{1,2,3,3}的RDD进行基本的RDD转化操作
函数名目的示例结果
map()将函数应用于RDD中的每个元素,将返回值构成新的RDDrdd.map(x => x + 1{2,3,4,4}
flatMap()将函数应用于RDD中的每个元素,将返回的迭代器的所有内容构成新的RDD。通常用来切分单词rdd.flatMap(x => x.to(3)){1,2,3,2,3,3,3}
filter()返回一个由通过传给filter()的函数的元素组成的RDDrdd.filter(x => x!=1){2,3,3}
distinct()去重rdd.distinct(){1,2,3}
sample(withReplacement, fraction, [seed])对RDD采样,以及是否替换rdd.sample(false, 0.5){1,2,3}非确定
对数据分别为{1,2,3}和{3,4,5}的RDD进行针对两个RDD的转化操作
函数名目的示例结果
union()生成一个包含两个RDD中所有元素的RDDrdd.union(other){1,2,3,3,4,5}
intersection()求两个RDD共同的元素的RDDrdd.intersection(other){3}
subtract()移除一个RDD中的内容(例如移除训练数据)rdd.subtract(other){1,2}
cartesian()与另一个RDD的笛卡尔积rdd.cartesian(other){(1,3),(1,4),...,(3,5)}

行动操作

你很有可能会用到基本RDD上最常见的行动操作reduce()。它接收一个函数作为参数,这个函数要操作两个RDD的元素类型的数据并返回一个同样类型的新元素。一个简单的例子就是函数+,可以用它来对我们的RDD进行累加。使用reduce(),可以很方便地计算出RDD中所有元素的总和、元素的个数,以及其他类型的聚合操作

        JavaRDD<String> counts = sc.textFile("README.md");
        Integer sum = Integer.valueOf(counts.reduce(
                (Function2<String, String, String>) (s, s2)
                        -> String.valueOf(Integer.valueOf(s) + Integer.valueOf(s2))
        ));

fold()和reduce()类似,接收一个与reduce()接收的函数签名相同的函数,再加上一个”初始值“来作为每个分区第一次调用时的结果。你所提供的初始值应当是你提供的操作的单位元素;也就是说,使用你的函数对这个初始值进行多次计算不会改变结果(例如+对应的0,*对应的1,或拼接操作对应的空列表)。

fold()和reduce()都要求函数的返回值类型需要和我们所操作的RDD中的元素类型相同。这很符合像sum这种操作的情况。但有时我们确实需要返回一个不同类型的值。例如,在计算平均值时,需要记录遍历过程中的计数以及元素的数量,这就需要返回一个二元组。可以先对数据使用map()操作,来把元素转为元素和1的二元组,也就是我们所希望的返回类型。这样reducce()就可以以二元组的形式进行归约了。

aggregate()函数则把我们从返回值类型必须与所操作的RDD类型相同的限制中解放出来。与fold()类似,使用aggregate()时,需要提供我们期待返回的类型的初始值。然后通过一个函数把RDD中的元素合并起来放入累加器。考虑到每个节点是在本地进行累加的,最终,还需要提供第二个函数来将累加器两两合并。

我们可以用aggregate()来计算RDD的平均值,来代替map()后面接fold()的方式

/**
 * @author DKing
 * @description
 * @date 2019/6/2
 */
public class CalculateSum {
    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setMaster("local").setAppName("calculateSum");
        JavaSparkContext sc = new JavaSparkContext(conf);

        JavaRDD<Integer> counts = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));

        Function2<AvgCount, Integer, AvgCount> addAndCount =
                (Function2<AvgCount, Integer, AvgCount>) (avgCount, integer) -> {
                    avgCount.total += integer;
                    avgCount.num += 1;
                    return avgCount;
                };
        Function2<AvgCount, AvgCount, AvgCount> combine =
                (Function2<AvgCount, AvgCount, AvgCount>) (avgCount, avgCount2) -> {
                    avgCount.total += avgCount2.total;
                    avgCount.num += avgCount2.num;
                    return avgCount;
                };

        AvgCount initial = new AvgCount(0, 0);
        AvgCount result = counts.aggregate(initial, addAndCount, combine);
        System.out.println(result.avg());
    }

    static class AvgCount implements Serializable {
        public int total;
        public int num;

        public AvgCount(int total, int num) {
            this.total = total;
            this.num = num;
        }

        public double avg() {
            return total / (double) num;
        }
    }
}
对一个数据为{1,2,3,3}的RDD进行基本的RDD行动操作
函数名目的示例结果
collect()返回RDD中的所有元素rdd.collect(){1,2,3,3}
count()RDD中的元素个数rdd.count()4
countByValue()各元素在RDD中出现的次数rdd.countByValue(){(1,1),(2,1),(3,2)}
take(num)从RDD中返回num个元素rdd.take(2){1,2}
takeOrdered(num)(ordering)从RDD中按照提供的顺序返回最前面的num个元素rdd.takeOrdered(2)(myOrdering){3,3}
takeSample(withReplacement,num,[seed])从RDD中返回任意一些元素rdd.takeSample(false,1)非确定的
reduce(func)并行整合RDD中所有数据rdd.reduce((x,y) => x+y)9
fold(zero)(func)和reduce()一样,但是需要提供初始值rdd.fold(0)((x,y) => x+y)9
aggregate(zeroValue)(seq0p,comb0p)和reduce()相似,但是通常返回不同类型的函数rdd.aggregate((0,0))(9,4)
top(num)从RDD中返回最前面的num个元素rdd.top(2){3,3}
foreach(func)对RDD中的每个元素使用给定的函数rdd.foreach(func)

在不同RDD类型间转换

在Java中,各种RDD的特殊类型间的转换更为明确。Java中有个专门的类JavaDoubleRDD和JavaPairRDD,来处理特殊类型的RDD,这两个类还针对这些类型提供了额外的函数。这让你可以更加了解所发生的一切。

Java中针对专门类型的函数接口
函数名等价函数用途
DoubleFlatMapFunction<T>Function<T,Iterable<Double>>用于flatMapToDouble,以生成DoubleRDD
DoubleFunction<T>Function<T,Double>用于mapToDouble,以生成DoubleRDD
PairFlatMapFunction<T,K,V>Function<T,Iterable<Tuple2<K,V>>>用于flatMapToPair,以生成PairRDD<K,V>
PairFunction<T,K,V>Function<T,Tuple2<K,V>>用于mapToPair,以生成PairRDD<K,V>
        JavaRDD<Integer> counts = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));

        JavaDoubleRDD doubleRDD = counts.mapToDouble(
                (DoubleFunction<Integer>) integer -> integer * integer
        );
        System.out.println(doubleRDD.mean());

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值