前两题的链接
2021年安徽省大数据与人工智能应用竞赛——MapReduce(数据预处理)题目解答
2021年安徽省大数据与人工智能应用竞赛——MapReduce(数据预处理)题目解答(第二题)
请使用MapReduce统计 calls.txt中的 被叫省份中 被叫次数最高的前三条记录
返回格式:省 ,被叫号码,被叫次数
数据calls.txt 通话记录
样例:18620192711,15733218050,1506628174,1506628265,650000,810000
字段分别为:
呼叫者手机号,接受者手机号,开始时间戳,接受时间戳,呼叫者地址省份编码,接受者地址省份编码
package Demo.mapreduce;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.log4j.BasicConfigurator;
import java.io.IOException;
import java.net.URI;
import java.util.*;
public class subject3 {
public static class demoMapper extends Mapper<LongWritable,Text,Text,Text> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split(",");
String province = split[5];
String phone = split[1];
context.write(new Text(province),new Text(phone));
}
}
public static class demoReducer extends Reducer<Text,Text,Text,IntWritable> {
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//定义一个map集合用来存放手机号码,和手机号出现的次数
Map<String,Integer> map = new HashMap<>();
for (Text value : values) {
//遍历values,获取当前省内的每一个手机号
String val = value.toString();
//getOrDefault(key,defaultValue)
//按照key查询Map里面的value值,查询成功则返回对应的value值,如果没有找到则返回defaultValue作为结果
Integer receive_num = map.getOrDefault(val, 0);
receive_num++;
//map集合自动去重,插入相同key的键值对,会自动覆盖之前的键值对
map.put(val,receive_num);
}
//将Map的所有键值对对象Entry<>作为元素放入list集合中
List<Map.Entry<String,Integer>> list = new ArrayList<>(map.entrySet());
//利用Collentions.sort对象list列表里面的元素进行排序
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
int count = 0;
for (Map.Entry<String, Integer> entry : list) {
if(count<3){
count++;
String phone = entry.getKey();
int receive_num = entry.getValue();
System.out.println(phone+"---"+receive_num);
context.write(new Text(key+","+phone),new IntWritable(receive_num));
}
}
}
}
public static void main(String[] args) throws Exception{
BasicConfigurator.configure();
// 配置mapreduce
Job job = Job.getInstance();
job.setJobName("zhang");
job.setJarByClass(subject3.class);
job.setMapperClass(demoMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setReducerClass(demoReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定路径
Path input1 = new Path("hdfs://master:9000/data/calls.txt");
FileInputFormat.addInputPath(job,input1);
Path output = new Path("hdfs://master:9000/output");//输出路径不能已存在
//获取文件系统对象fs,利用fs来对hdfs中的文件进行操作
FileSystem fs = FileSystem.get(new URI("hdfs://master:9000"),new Configuration());
if(fs.exists(output)){
fs.delete(output,true);
}
FileOutputFormat.setOutputPath(job,output);
//启动
job.waitForCompletion(true);
}
}
map阶段只需要将省份作为key,将接收者电话号码作为value传入reduce端即可。
但是在reduce端要对手机号码进行统计数量以及排序,因此需要分别解决这两个问题,才能获取最终结果
这里的思路是,定义一个HashMap集合,将手机号码作为key,被叫次数作为value。Map集合中不允许出现重复的key值,所以一旦有新的<手机号码,被叫次数>放入map集合中,就会覆盖原先的相同key的键值对。
确定了这一点后,还需要做到如何修改手机号码的被叫次数,否则每次都是一样的key-value,即使覆盖也没有意义。这里采取的方式,首先遍历values,也就是当前分区的全部手机号码,然后利用hashmap的一个方法
public V getOrDefault(Object key, V defaultValue)
这个方法的作用是按照key查询Map里面的value值,查询成功则返回对应的value值,如果没有找到则返回defaultValue作为结果。在一开始,map集合为空,所以必然返回defaultValue的值也就是0给receive_num,即被叫次数。下面再借助receive_num++,让0变成1。
然后第二次按照这个key查询时,获取到了value值为1,此时说明有两个相同的手机号码,应该累加在一起,receive_num++,变成2,然后<手机号码,2>会覆盖掉原先<手机号码,1>
这样就完成了数量的统计
TreeSet也可以对元素进行排序,但是选择list而不是TreeSet的原因是,list集合的排序可以通过 Collections.sort触发,而TreeSet是将元素加入到Set集合里面的时候触发排序。这里要将所有Map集合里面的键值对对象传入后,整体一起排序,触发比较器,所以选择list集合更合适。
TreeMap也可以进行排序,但是如果想让元素传入TreeMap的时候按顺序排列,就需要在定义TreeMap的时候传入比较器,实现Comparator接口。但是这里实现Comparator接口,就会很难实现Compare方法。所以还是使用将map元素放入list集合的方式最为省力。
另外还要求取前三条记录,所以加上一个次数判断,仅在count<3的时候才会输出