大数据框架之一——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实现类

image.png

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;
    }
}
posted @ 2024-08-08 15:31  shmil  阅读(9)  评论(1编辑  收藏  举报