3. 将CanData类型的RDD转成键值对RDD,key是“车型_车架号”,value是1
6.用filter算子将车型为E100的10辆车的每辆总数量输出到控制台
- 了解普通RDD和键值对RDD的区别
- 理解filter、mapToPair、groupByKey转换算子的作用,并能应用到具体业务场景中
- 在此次任务中需要准备相应的json文件以及spark环境的搭建
提示:以下是本篇文章正文内容,下面案例可供参考
一、spark统计每辆车上传的总数据量
1.读入数据,数据的获取是必不可少的
代码如下(示例):
import com.google.gson.Gson;
import com.lzzy.mk2_2.CanData;
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.List;
public static void main(String[] args) {
SparkConf sparkConf = new SparkConf().setAppName("Module2Task3").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(sparkConf);
// 读文件,得到RDD
JavaRDD<String> srcRdd = sc.textFile("D:\\spark_demo\\2023-9-27\\car.json");
2.将string转成CanData
用于筛选出满足特定条件的元素,并返回一个新的RDD。
参数:Function<T, Boolean>,该接口是函数式接口,T是它的call方法的形参类型,和RDD的元素类型一致,Boolean是call方法的返回值类型
返回值:一个新的RDD对象。
// 将string转成CanData
JavaRDD<CanData> canDataRdd = srcRdd.map(new Function<String, CanData>() {
@Override
public CanData call(String s) throws Exception {
Gson gson = new Gson();
CanData canData = gson.fromJson(s, CanData.class);
return canData;
}
});
3. 将CanData类型的RDD转成键值对RDD,key是“车型_车架号”,value是1
键值对RDD
是指每个RDD元素都是(Key, Value)键值类型。
普通RDD和键值对RDD的区别:
RDD 类名不同(java) 每个数据含义不同
普通RDD JavaRDD<T> 普通RDD中一个数据代表一个单独的值。比如一个整数或者一个字符串或者一个其他复杂的对象
键值对RDD JavaPairRDD<K,V> 键值对RDD中一个数据是一个元组(Tuple),在元组中第一个元素是key值,第二个元素value值。
key和value的类型可以是简单类型也可以是复杂的object或者另一个元组的结合。(用Tuple2<T1, T2>这个类来表示元组。T1是键的数据类型,T2是值的数据类型)
JavaPairRDD<String, Integer> kvRdd = canDataRdd.mapToPair(new PairFunction<CanData, String, Integer>() {
@Override
public Tuple2<String, Integer> call(CanData canData) throws Exception {
String vehType = canData.getVehType();
String vin = canData.getVin();
String key = vehType + "_" + vin;
Integer val = 1;
return new Tuple2<>(key, val);
}
});
4.分组:将相同车架号的数据合并在一起
转换算子之groupByKey
按照key进行分组,将具有相同键的数据合并在一起,返回一个新的键值对RDD。
(2023-01-01, 1)、(2023-01-01, 2)、(2023-01-01, 3)、(2023-01-02, 1)、(2023-01-03, 1)
调用kvRDD1.groupByKey()后得到的RDD的数据为:
(2023-01-01, [1,2,3])、(2023-01-02, [1])、(2023-01-03, [1])
示例:
JavaPairRDD<String, Iterable<Integer>> gRdd = kvRdd.groupByKey();
// 使用map算子转换分组后的键值对RDD,将每个键值对转成一个Tuple2对象,key是车架号,value是这辆车的总数据量
// gRdd : ("vin0001", [1,1,1,1]) -> ("vin0001", 4)
// ("vin0002". [1,1]) -> ("vin0002", 2)
JavaRDD<Tuple2<String, Integer>> resRdd = gRdd.map(new Function<Tuple2<String, Iterable<Integer>>, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> call(Tuple2<String, Iterable<Integer>> t) throws Exception {
String vin = t._1.split("_")[1];
Iterable<Integer> values = t._2;
int sum = 0;
for(Integer a : values) {
sum += 1;
}
return new Tuple2<>(vin, sum);
}
});
5.获取10辆车并将结果输出到控制台
List<Tuple2<String, Integer>> take = resRdd.take(10);
for(Tuple2<String, Integer> t : take) {
System.out.print(t);
}
6.用filter算子将车型为E100的10辆车的每辆总数量输出到控制台
用于筛选出满足特定条件的元素,并返回一个新的RDD。
参数:Function<T, Boolean>,该接口是函数式接口,T是它的call方法的形参类型,和RDD的元素类型一致,Boolean是call方法的返回值类型
返回值:一个新的RDD对象。
工作过程:假设调用了rdd1.filter(),filter内部遍历rdd1的所有元素,每处理一个元素,就会调用call方法,并将元素作为实参传给call方法,call方法根据业务逻辑,决定返回true还是false,如果返回true,则当前rdd元素就作为新的RDD对象的一个元素。
// 用filter算子将车型为E100的10辆车的每辆总数量输出到控制台
JavaPairRDD<String, Iterable<Integer>> e100 = gRdd.filter(new Function<Tuple2<String, Iterable<Integer>>, Boolean>() {
@Override
public Boolean call(Tuple2<String, Iterable<Integer>> t) throws Exception {
String vehType = t._1.split("_")[0];
return "E100".equals(vehType);
}
});
JavaRDD<Tuple2<String, Integer>> e100Res = e100.map(new Function<Tuple2<String, Iterable<Integer>>, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> call(Tuple2<String, Iterable<Integer>> t) throws Exception {
String vin = t._1.split("_")[1];
Iterable<Integer> values = t._2;
int sum = 0;
for(Integer a : values) {
sum += 1;
}
return new Tuple2<>(vin, sum);
}
});
System.out.println(e100Res.take(10));
// sc.stop();
}
}
7.心得体会
首先,良好的代码结构和设计能力是非常重要的。通过模块化设计和面向对象编程,可以使代码的结构清晰,易于理解、维护和扩展。同时,考虑到可读性,采用清晰的命名和注释,使得代码更容易被其他开发人员理解和使用。
其次,优化的高效性能是一个好的程序员必须具备的特质。在代码的实现过程中,应该尽可能地考虑到代码的效率和执行速度,以确保代码执行的效率和性能。
第三,出色的错误处理机制和安全性意识是不可或缺的。当代码出现异常情况时,能够及时捕获并处理,确保程序的稳定性和可靠性;同时,在设计代码时,需要关注潜在的安全漏洞和攻击。
第四,代码重用性和可配置性也是非常重要的。通过合理的函数、类设计实现代码的复用,可以提高开发效率;而通过灵活的配置,代码可以适应不同的需求和环境。
最后,完善的单元测试和调试功能以及配套详尽的文档说明也是不可或缺的。这些措施可以确保代码的正确性和稳定性,并提供代码的功能、用法等信息,方便其他开发人员使用。
8.总结
模块化设计和面向对象编程使代码结构清晰,易于理解和扩展。
优化的高效性能确保代码在执行时具有卓越的速度和效率。
强调可读性,使用清晰的命名和良好的注释,使得代码易于阅读和维护。
考虑到可扩展性,代码能够轻松地添加新功能或模块。
出色的错误处理机制能够处理异常情况,保证程序的稳定性和可靠性。
高度关注安全性,采取措施防范潜在的安全漏洞和攻击。
具备代码重用性,通过合理的函数、类设计实现代码的复用,提高了开发效率。
清晰的逻辑结构使代码易于理解,降低错误发生的可能性。
包含单元测试和调试功能,确保代码的正确性和稳定性。
配套详尽的文档说明,提供代码的功能、用法等信息,方便其他开发人员使用。
异常处理机制捕获并处理可能发生的异常情况,确保程序的稳定性。
可配置性使得代码能够灵活适应不同需求和环境。