大数据框架之一——Hadoop学习第三天
1、MapReduce概述及原理
-
MapReduce是一种分布式计算模型,由Google提出,主要用于搜索领域,解决海量数据的计算问题.
-
MapReduce是分布式运行的,由两个阶段组成:Map和Reduce,Map阶段是一个独立的程序,有很多个节点同时运行,每个节点处理一部分数据。Reduce阶段是一个独立的程序,有很多个节点同时运行,每个节点处理一部分数据【在这先把reduce理解为一个单独的聚合程序即可】.
-
MapReduce框架都有默认实现,用户只需要覆盖map()和reduce()两个函数,即可实现分布式计算,非常简单.
- 这两个函数的形参和返回值都是<key、value>,使用的时候一定要注意构造<k,v>。
2、WordCount计算
2.1常用的Writable实现类
- MapReduce中对应数据格式为Key-Value格式,并且Key和Value都是Writable实现类
2.2MapReduce计算-WordCount
- 主函数入口代码如下
package com.mr.worcount;
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.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.FileNotFoundException;
import java.io.IOException;
public class WordCount {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
// TODO MapReduce程序入口中的固定写法
// TODO 1.获取Job对象 并设置相关Job任务的名称及入口类
// Job job = new Job();
// job.setJobName("word count");
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "word count");
// 设置当前main方法所在的入口类
job.setJarByClass(WordCount.class);
// TODO 2.设置自定义的Mapper和Reducer类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// TODO 3.设置Mapper的KeyValue输出类 和 Reducer的输出类 (最终输出)
job.setMapOutputKeyClass(IntWritable.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(IntWritable.class);
// 设置ReduceTask的数量为2
job.setNumReduceTasks(2);
// TODO 4.设置数据的输入和输出路径
// org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
// org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
//在HDFS上进行文件的读取和写出
// TextInputFormat.addInputPath(job,new Path("/data/words.txt"));
// TextOutputFormat.setOutputPath(job,new Path("/api/wordCount"));
// 本地路径
FileSystem fileSystem = FileSystem.get(job.getConfiguration());
Path outPath = new Path("hadoop/out/wordCount");
// Path inpath = new Path("hadoop/data/words.txt");
Path inpath = new Path("hadoop/data/words");
if (!fileSystem.exists(inpath)) {
throw new FileNotFoundException(inpath+"不存在");
// System.out.println(inpath+"不存在");
// System.exit(1);
}
// TextInputFormat.addInputPath(job,inpath);
FileInputFormat.addInputPath(job,inpath);
if (fileSystem.exists(outPath)) {
System.out.println("路径存在,开始删除");
fileSystem.delete(outPath,true);
}
// TextOutputFormat.setOutputPath(job,outPath);
FileOutputFormat.setOutputPath(job,outPath);
// TODO 5.提交任务开始执行
job.waitForCompletion(true);
}
}
- Mapper代码
package com.mr.worcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
TODO MapTask阶段
自定义类继承Mapper,该Mapper类为一个具体的类,并其中定义了一些泛型
<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
通过之前的学习,知道MapTask阶段需要编写map函数,定义数据处理的逻辑
KEYIN: 表示输入的Key的类型 表示map函数处理的Key类型 变量保存的数据是偏移量
读取数据的位置 字节数的位置非常大,需要使用Long类型 => LongWritable
VALUEIN: 表示输入的Value类型 表示map函数处理的Value类型 表示的是一行字符串数据 String => Text
KEYOUT: 表示输出的Key的类型 根据要处理的数据逻辑来进行定义 => 输出的Key为单词 => Java中的String类型 => Hadoop中的Text
VALUEOUT:表示输出的Value的类型 根据要处理的数据逻辑来进行定义 => 输出的Value为1 => Java中的int类型 => Hadoop中的IntWritable
注意:当数据在Hadoop中进行传递时,需要进行序列化,而Java中的序列化内容多,比较重,导致网络IO开销大
为了计算速度快,Hadoop提供一套新的序列化类型
*/
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
/**
* map函数中定义了Task任务在Map阶段所做的数据处理任务
* 当前函数中需要对获取到的一行字符串进行按照 空格切分,再将单词遍历 之后再形成 Key为单词 1为Value的数据形式
* TODO 注意:map方法在执行的过程中是一行数据对应调用一次该函数
* @param key 变量保存的数据是偏移量
* @param value 表示的是一行字符串数据 是从文本文件中按行读取出来的
* @param context 表示的是 Mapper.Context的上下文对象,作用是连接 Map阶段和Reduce阶段的桥梁
*/
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
// value遍历中的数据 => hello hadoop
// TODO 获取到的一行字符串进行按照 空格切分
String[] words = value.toString().split(" ");
// TODO 再将单词遍历
for (String word : words) {
// TODO 形成Key为单词 1为Value的数据形式
// context 对象可以将Map阶段生成的数据发送给reduce阶段
context.write(new Text(word),new IntWritable(1));
}
}
}
- Reducer阶段
package com.mr.worcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/*
TODO ReduceTask阶段
自定义类继承Reducer,该Reducer类为一个具体的类,并其中定义了一些泛型
<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
Reduce阶段的数据是由Map阶段发送过来的,所以Map阶段输出的类型就是Reduce阶段接收的类型
根据处理逻辑:
KEYIN: Text
VALUEIN: IntWritable
根据数据最终的要求:
KEYOUT, VALUEOUT 表示最终每个单词出现的次数
KEYOUT : Text
VALUEOUT: IntWritable
*/
public class WordCountReducer extends Reducer<Text, IntWritable,Text, IntWritable> {
/**
* reduce函数中定义了 Reduce阶段中要执行的代码逻辑
* 将相同单词的KeyValue数据汇集到一起,再将所有的Value值 1 进行相加 得到最终的结果
* TODO 注意:① 对于reduce函数需要等Mapper阶段执行完成后才能再执行
* ② 对于每个Key会调用一次reduce函数
* ③ 对于Key的处理是存在有先后顺序的 按照字典序进行排序
* @param key 表示map端输出的Key数据 单词
* @param values 类型为Iterable 表示相同Key的Value数据形成的迭代器
* @param context 上下文对象 可以将数据写出到HDFS
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
// 定义num 用于记录单词出现的次数
int num = 0;
// TODO 再将所有的Value值 1 进行相加 得到最终的结果
for (IntWritable value : values) {
num += value.get();
}
context.write(key,new IntWritable(num));
}
}
3、Map Reduce关联分析
3.1案例:针对学生的各科成绩数据进行汇总
①主函数
package com.mr.count;
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.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Count {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
// TODO MapReduce程序入口中的固定写法
// TODO 1.获取Job对象 并设置相关Job任务的名称及入口类
// Job job = new Job();
// job.setJobName("word count");
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "ReduceJoin");
// 设置当前main方法所在的入口类
job.setJarByClass(Count.class);
// TODO 2.设置自定义的Mapper和Reducer类
job.setMapperClass(CountMapper.class);
job.setReducerClass(CountReducer.class);
// TODO 3.设置Mapper的KeyValue输出类 和 Reducer的输出类 (最终输出)
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// TODO 4.设置数据的输入和输出路径
// 本地路径
FileSystem fileSystem = FileSystem.get(job.getConfiguration());
Path outPath = new Path("hadoop/out/count");
Path inpath = new Path("hadoop/data/score.txt");
if (!fileSystem.exists(inpath)) {
throw new FileNotFoundException(inpath+"不存在");
}
TextInputFormat.addInputPath(job,inpath);
// TextInputFormat.addInputPath(job,inpath);
if (fileSystem.exists(outPath)) {
System.out.println("路径存在,开始删除");
fileSystem.delete(outPath,true);
}
TextOutputFormat.setOutputPath(job,outPath);
// TODO 5.提交任务开始执行
job.waitForCompletion(true);
}
}
②对应的Mapper代码
package com.mr.count;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
TODO
在编写代码之前需要先定义数据的处理逻辑
MapTask阶段:
根据读取的一行数据 进行按照,进行切分 获取学生ID和学生成绩 将学生ID作为key 成绩作为Value写出到 Reduce
ReduceTask阶段:
根据相同的学生ID将所有的成绩数据作为values获取到 形成迭代器,再进行遍历求其总和
KEYIN: 表示输入的Key的类型 表示map函数处理的Key类型 变量保存的数据是偏移量
读取数据的位置 字节数的位置非常大,需要使用Long类型 => LongWritable
VALUEIN: 表示输入的Value类型 表示map函数处理的Value类型 表示的是一行字符串数据 String => Text
KEYOUT: 学生ID => 字符串 => Text
VALUEOUT: 各科的成绩 => int => IntWritable
*/
public class CountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
/**
* map函数中定义了Task任务在Map阶段所做的数据处理任务
* 根据读取的一行数据 进行按照,进行切分 获取学生ID和学生成绩 将学生ID作为key 成绩作为Value写出到 Reduce
*
* @param key 变量保存的数据是偏移量
* @param value 表示的是一行字符串数据 是从文本文件中按行读取出来的
* @param context 表示的是 Mapper.Context的上下文对象,作用是连接 Map阶段和Reduce阶段的桥梁
*/
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
String[] columns = value.toString().split(",");
if (columns.length == 3) {
String id = columns[0];
String score = columns[2];
context.write(new Text(id),new IntWritable(Integer.valueOf(score)));
}
}
}
③Reducer阶段
package com.mr.count;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/*
TODO ReduceTask阶段
自定义类继承Reducer,该Reducer类为一个具体的类,并其中定义了一些泛型
<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
Reduce阶段的数据是由Map阶段发送过来的,所以Map阶段输出的类型就是Reduce阶段接收的类型
根据处理逻辑:
KEYIN: Text
VALUEIN: IntWritable
根据数据最终的要求:
KEYOUT, VALUEOUT 表示最终每个单词出现的次数
KEYOUT : Text
VALUEOUT: IntWritable
*/
public class CountReducer extends Reducer<Text, IntWritable,Text, IntWritable> {
/**
* reduce函数中定义了 Reduce阶段中要执行的代码逻辑
* 根据相同的学生ID将所有的成绩数据作为values获取到 形成迭代器,再进行遍历求其总和
* @param key 表示map端输出的Key数据 学生ID
* @param values 类型为Iterable 表示相同学生ID的成绩数据
* @param context 上下文对象 可以将数据写出到HDFS
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
int totalScore= 0;
for (IntWritable score : values) {
totalScore += score.get();
}
context.write(key,new IntWritable(totalScore));
}
}
3.2ReduceJoin
- 在map阶段,map函数同时读取两个文件File1和File2,注意区分的方法
①主入口
package com.mr.reduceJoin;
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.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ReduceJoin {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
// TODO MapReduce程序入口中的固定写法
// TODO 1.获取Job对象 并设置相关Job任务的名称及入口类
// Job job = new Job();
// job.setJobName("word count");
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "ReduceJoin");
// 设置当前main方法所在的入口类
job.setJarByClass(ReduceJoin.class);
// TODO 2.设置自定义的Mapper和Reducer类
job.setMapperClass(ReduceJoinMapper.class);
job.setReducerClass(ReduceJoinReducer.class);
// TODO 3.设置Mapper的KeyValue输出类 和 Reducer的输出类 (最终输出)
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
// TODO 4.设置数据的输入和输出路径
// 本地路径
FileSystem fileSystem = FileSystem.get(job.getConfiguration());
Path outPath = new Path("hadoop/out/reducejoin");
// TODO 对输入路径添加了学生基本信息数据和总分数据
Path studentInpath = new Path("hadoop/data/students.txt");
Path totalScoreInpath = new Path("hadoop/out/count");
if (!fileSystem.exists(studentInpath)) {
throw new FileNotFoundException(studentInpath+"不存在");
}
TextInputFormat.addInputPath(job,studentInpath);
if (!fileSystem.exists(totalScoreInpath)) {
throw new FileNotFoundException(totalScoreInpath+"不存在");
}
TextInputFormat.addInputPath(job,totalScoreInpath);
if (fileSystem.exists(outPath)) {
System.out.println("路径存在,开始删除");
fileSystem.delete(outPath,true);
}
TextOutputFormat.setOutputPath(job,outPath);
// TODO 5.提交任务开始执行
job.waitForCompletion(true);
}
}
②MapTask阶段
package com.mr.reduceJoin;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
TODO
在编写代码之前需要先定义数据的处理逻辑
MapTask阶段
① 对于MapTask需要对不同文件的数据进行判断
② 将读取到的字符串进行切分处理,将学生ID作为Key,其他信息作为Value写出到ReduceTask阶段
其中其他信息包含: 学生的所有基本信息和 学生的成绩数据 两类
注意:
对于Mapper阶段的输出KeyValue类型为 Text Text
ReduceTask阶段
① 接收的数据类型,就是MapTask阶段输出的数据类型
② 针对相同Key的Value数据进行判断是否为成绩数据或者基本信息数据
③ 如果判断出来,再拼接成一个整的字符串,再进行输出
*/
public class ReduceJoinMapper extends Mapper<LongWritable, Text, Text, Text> {
/**
* 通过Debug可以看到对于多个文件数据,先读取students.txt数据再读取 count的结果数据
*/
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context) throws IOException, InterruptedException {
String readLine = value.toString();
if (readLine.contains(",")) {
String[] columns = readLine.split(",");
if (columns.length == 5){
String id = columns[0];
String name = columns[1];
String age = columns[2];
String gender = columns[3];
String clazz = columns[4];
context.write(new Text(id),new Text(name+","+age+","+gender+","+clazz));
}
}else {
String[] columns = readLine.split("\t");
if (columns.length == 2){
String id = columns[0];
String score = columns[1];
context.write(new Text(id),new Text(score));
}
}
}
}
③ReduceTask阶段
package com.mr.reduceJoin;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/*
TODO ReduceTask阶段
自定义类继承Reducer,该Reducer类为一个具体的类,并其中定义了一些泛型
<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
Reduce阶段的数据是由Map阶段发送过来的,所以Map阶段输出的类型就是Reduce阶段接收的类型
根据处理逻辑:
KEYIN: Text
VALUEIN: IntWritable
根据数据最终的要求:
KEYOUT, VALUEOUT 表示最终每个单词出现的次数
KEYOUT : Text
VALUEOUT: IntWritable
*/
public class ReduceJoinReducer extends Reducer<Text, Text,Text, Text> {
/**
* ReduceJoin 是指数据关联是产生再Reduce阶段
*/
@Override
protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
String info ="";
String score ="";
for (Text value : values) {
if (!value.toString().contains(",")){
// 当长度为1表示学生的成绩数据
score = value.toString();
}else {
// 学生的基本信息数据
info = value.toString();
}
}
context.write(key,new Text(info+","+score));
}
}
4、MapReduce过滤
- 在MapReduce过程中可以根据判断逻辑选择适当的数据进行写出,同时MapReduce过程中允许只存在有Map过程
4.1过滤出总分大于450分的同学
①主函数
package com.mr.filter.more450;
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.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FilterMore450 {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
// TODO MapReduce程序入口中的固定写法
// TODO 1.获取Job对象 并设置相关Job任务的名称及入口类
// Job job = new Job();
// job.setJobName("word count");
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "FilterMore450");
// 设置当前main方法所在的入口类
job.setJarByClass(FilterMore450.class);
// TODO 2.设置自定义的Mapper和Reducer类
job.setMapperClass(FilterMore450Mapper.class);
// TODO 3.设置Mapper的KeyValue输出类 (最终输出)
job.setMapOutputKeyClass(Text.class);
// job.setMapOutputValueClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(Text.class);
// job.setOutputValueClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// TODO 4.设置数据的输入和输出路径
// 本地路径
FileSystem fileSystem = FileSystem.get(job.getConfiguration());
Path outPath = new Path("hadoop/out/filterMore450");
Path inpath = new Path("hadoop/out/reducejoin");
if (!fileSystem.exists(inpath)) {
throw new FileNotFoundException(inpath+"不存在");
}
TextInputFormat.addInputPath(job,inpath);
if (fileSystem.exists(outPath)) {
System.out.println("路径存在,开始删除");
fileSystem.delete(outPath,true);
}
TextOutputFormat.setOutputPath(job,outPath);
// TODO 5.提交任务开始执行
job.waitForCompletion(true);
}
}
②MapTask阶段
package com.mr.filter.more450;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
TODO
在编写代码之前需要先定义数据的处理逻辑
MapTask阶段:
可以直接读取ReduceJoin的结果数据
注意:
① 对于MapReduce可以只有Mapper阶段
② 对于输出的数据,可以只有Key 对于Value的数据类型从可以使用 NullWritable
*/
public class FilterMore450Mapper extends Mapper<LongWritable, Text, Text, NullWritable> {
/**
*
*/
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
// 1500100008 符半双,22,女,理科六班,363
String oneLine = value.toString();
String[] split = oneLine.split("\t");
String id = split[0];
Integer score = Integer.valueOf(split[1].split(",")[4]);
if (score > 450){
context.write(new Text(id+","+split[1]),NullWritable.get());
}
}
}
4.2对于各班级中的学生总分进行排序,要求取出各班级中总分前三名学生
①主入口
package com.mr.groupby;
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.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Top3 {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
// TODO MapReduce程序入口中的固定写法
// TODO 1.获取Job对象 并设置相关Job任务的名称及入口类
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "Sort");
// 设置当前main方法所在的入口类
job.setJarByClass(Top3.class);
// TODO 2.设置自定义的Mapper和Reducer类
job.setMapperClass(Top3Mapper.class);
job.setReducerClass(Top3Reducer.class);
// TODO 3.设置Mapper的KeyValue输出类 和 Reducer的输出类 (最终输出)
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// TODO 4.设置数据的输入和输出路径
// 本地路径
FileSystem fileSystem = FileSystem.get(job.getConfiguration());
Path outPath = new Path("hadoop/out/top3");
Path inpath = new Path("hadoop/out/reducejoin");
if (!fileSystem.exists(inpath)) {
throw new FileNotFoundException(inpath+"不存在");
}
TextInputFormat.addInputPath(job,inpath);
if (fileSystem.exists(outPath)) {
System.out.println("路径存在,开始删除");
fileSystem.delete(outPath,true);
}
TextOutputFormat.setOutputPath(job,outPath);
// TODO 5.提交任务开始执行
job.waitForCompletion(true);
}
}
②MapTask阶段
package com.mr.groupby;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
TODO
在编写代码之前需要先定义数据的处理逻辑
对于各班级中的学生总分进行排序,要求取出各班级中总分前三名学生
MapTask阶段:
① 读取ReduceJoin的处理结果,并对数据进行提取
② 按照学生的班级信息,对班级作为Key,整行数据作为Value写出到 ReduceTask 端
ReduceTask阶段:
① 接收到整个班级中的所有学生信息并将该数据存放在迭代器中
*/
public class Top3Mapper extends Mapper<LongWritable, Text, Text, Text> {
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context) throws IOException, InterruptedException {
// 1500100009 沈德昌,21,男,理科一班,251 => 表示读取到的数据
String[] split = value.toString().split("\t");
if (split.length == 2) {
String otherInfo = split[1];
String[] columns = otherInfo.split(",");
if (columns.length == 5) {
String clazz = columns[3];
context.write(new Text(clazz), value);
}
}
}
}
③ReduceTask阶段
package com.mr.groupby;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/*
TODO ReduceTask阶段
自定义类继承Reducer,该Reducer类为一个具体的类,并其中定义了一些泛型
<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
Reduce阶段的数据是由Map阶段发送过来的,所以Map阶段输出的类型就是Reduce阶段接收的类型
根据处理逻辑:
KEYIN: Text
VALUEIN: IntWritable
根据数据最终的要求:
KEYOUT, VALUEOUT 表示最终每个单词出现的次数
KEYOUT : Text
VALUEOUT: IntWritable
*/
public class Top3Reducer extends Reducer<Text, Text,Text, NullWritable> {
/**
* 对一个班级中所有的学生成绩进行排序 =>
* 1.将数据存储在一个容器中
* 2.对容器中数据进行排序操作
* 对排序的结果进行取前三
* @param key 表示班级信息
* @param values 一个班级中所有的学生数据
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
// 1500100009 沈德昌,21,男,理科一班,251
List<Stu> stus = new ArrayList<>();
for (Text stu : values) {
String[] split = stu.toString().split("\t");
String[] columns = split[1].split(",");
stus.add(new Stu(split[0],columns[0],Integer.valueOf(columns[1]),columns[2],columns[3],Integer.valueOf(columns[4])));
}
// 对List中的数据进行排序操作
Collections.sort(
stus,
new Comparator<Stu>() {
@Override
public int compare(Stu o1, Stu o2) {
int compareScore = o1.score - o2.score;
return - compareScore > 0 ? 1: (compareScore == 0? o1.id.compareTo(o2.id):-1);
}
}
);
// 对排序的结果进行遍历
for (int i = 0; i < 3; i++) {
context.write(new Text(stus.get(i).toString()+","+(i+1)),NullWritable.get());
}
}
}
④Student类
package com.mr.groupby;
public class Stu {
String id;
String name;
int age;
String gender;
String clazz;
int score;
public Stu(String id, String name, int age, String gender, String clazz, int score) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
this.clazz = clazz;
this.score = score;
}
@Override
public String toString() {
return id +
", " + name +
", " + age +
", " + gender +
", " + clazz +
", " + score;
}
}