一、数据去重
问题描述
数据去重的最终目标是让原始数据中出现次数超过一次的数据在输出文件中只出现一次。
问题分析
根据reduce的过程特性,会自动根据key来计算输入的value集合
把数据作为key输出给reduce,无论这个数据出现多少次,reduce最终结果中key只能输出一次。
实现步骤
实例中每个数据代表输入文件中的一行内容,map阶段采用Hadoop默认的作业输入方式。
- 将value设置为key,并直接输出。 map输出数据的key为数据,将value设置成空值。
- 在MapReduce流程中,map的输出
<key,value>
经过shuffle过程聚集成<key,value-list>
后会交给reduce. - reduce阶段不管每个key有多少个value,它直接将输入的key复制为输出的key,并输出(输出中的value被设置成空)。
Map函数
public static class Map extends Mapper<Object,Text,Text,Text>{
private static Text line=new Text();//每行数据
//实现map函数
public void map(Object key,Text value,Context context)
throws IOException,InterruptedException{
line=value;
context.write(line, new Text(""));
}
}
Reduce函数
public static class Reduce extends Reducer<Text,Text,Text,Text>{
//实现reduce函数
public void reduce(Text key,Iterable<Text> values,Context context) {
throws IOException,InterruptedException{
context.write(key, new Text(""));
}
}
Main函数:
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
conf.set(“mapred.job.tracker”, “192.168.140.128:9001”);
Job job = new Job(conf, "Data Deduplication");
job.setJarByClass(Dedup.class);
//设置Map、Combine和Reduce处理类
job.setMapperClass(Map.class);
job.setCombinerClass(Reduce.class);
job.setReducerClass(Reduce.class);
//设置输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//设置输入和输出目录
FileInputFormat.addInputPath(job, new Path(“my_in”));
FileOutputFormat.setOutputPath(job, new Path(“my_out”));
//如果job运行成功了,我们的程序就会正常退出
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
二、数据排序
问题描述
对输入文件中数据进行排序。输入文件中的每行内容均为一个数字,即一个数据。要求在输出中每行有两个间隔的数字,其中,第一个代表原始数据在原始数据集中的位次,第二个代表原始数据。
问题分析
这个实例仅仅要求对输入数据进行排序。
MapReduce过程中就有排序,它的默认排序规则按照key值进行排序的,如果key为封装int的IntWritable类型,那么MapReduce按照数字大小对key排序,如果key为封装为String的Text类型,那么MapReduce按照字典顺序对字符串排序。
使用封装int的IntWritable型数据结构了。也就是在map中将读入的数据转化成IntWritable型,然后作为key值输出(value任意)。reduce拿到<key,value-list>
之后,将输入的key作为value输出,并根据value-list中元素的个数决定输出的次数。输出的key(即代码中的linenum)是一个全局变量,它统计当前key的位次。需要注意的是这个程序中没有配置Combiner,也就是在MapReduce过程中不使用Combiner。这主要是因为使用map和reduce就已经能够完成任务了。
实现步骤
- 在map中将读入的数据转化成IntWritable型,然后作为key值输出(value任意)。
- reduce拿到
<key,value-list>
之后,将输入的key作为value输出,并根据value-list中元素的个数决定输出的次数。 - 输出的key是一个全局变量,它统计当前key的位次。
Map函数
static class MyMapper extends Mapper <Object, Object, IntWritable, NullWritable>{
IntWritable out_key = new IntWritable();
NullWritable out_value = NullWritable.get();
protected void map(Object key, Object value, Context context)
throws IOException, InterruptedException {
//trim()去除空格
int val = Integer.parseInt(value.toString().trim());
out_key.set(val);
context.write(out_key, out_value);
}
}
Reduce函数
static class MyReduce extends Reducer <IntWritable, NullWritable, IntWritable, IntWritable> {
IntWritable out_key = new IntWritable();
int k = 0;
protected void reduce(IntWritable key, Iterable<NullWritable> values, Context context)
throws IOException, InterruptedException {
out_key.set(k++);
context.write(out_key, key);
}
}
如果map和reduce输出的key-value类型不一致,需要修改设置
job.setMapOutputKeyClass(IntWritable.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(IntWritable.class);
三、平均成绩
问题描述
对输入文件中数据进行计算学生平均成绩。输入文件中的每行内容均为一个学生的姓名和他相应的成绩,如果有多门学科,则每门学科为一个文件。要求在输出中每行有两个间隔的数据,其中,第一个代表学生的姓名,第二个代表其平均成绩。
问题分析
Map处理的是一个纯文本文件,文件中存放的数据时每一行表示一个学生的姓名和他相应一科成绩。Mapper处理的数据是由InputFormat分解过的数据集,其中InputFormat的作用是将数据集切割成小数据集InputSplit,每一个InputSlit将由一个Mapper负责处理。
InputFormat中还提供了一个RecordReader的实现,并将一个InputSplit解析成<key,value>
对提供给了map函数。InputFormat的默认值是TextInputFormat,它针对文本文件,按行将文本切割成InputSlit,并用LineRecordReader将InputSplit解析成<key,value>
对,key是行在文本中的位置,value是文件中的一行。
Map的结果会通过partion分发到Reducer,Reducer做完Reduce操作后,将通过以格式OutputFormat输出。
Mapper最终处理的结果对<key,value>
,会送到Reducer中进行合并,合并的时候,有相同key的键/值对则送到同一个Reducer上。Reducer是所有用户定制Reducer类地基础,它的输入是key和这个key对应的所有value的一个迭代器,同时还有Reducer的上下文。Reduce的结果由Reducer.Context的write方法输出到文件中。