大数据笔记25—Hadoop基础篇08 (MapReduce编程深入)


苟有恒,何必三更眠五更起;
最无益,莫过一日曝十日寒。

MapReduce编程深入

知识点01:课程回顾

  1. MapReduce中的Input的功能是什么?

    • 功能:负责程序的输出
    • 实现
      • 数据分片:将读取到的数据拆分为多个分片Split
      • 转换KV:将任何一种数据源转换为KV结构
  2. MapReduce的Map的功能是什么?

    • 功能:实现分布式中分的过程
    • 实现
      • 根据分片的个数启动MapTask,每个MapTask进程处理一个分片的数据
      • 每个MapTask会实例化一个Mapper类的对象,调用map方法实现处理
  3. MapReduce的Shuffle的功能是什么?

    • 功能:分区、排序、分组
    • 实现
      • 排序:按照K2,对Map输出的KV进行排序
      • 分组:按照K2,对Map输出的KV进行分组,相同K2对应的所有V2放在一起
  4. MapReduce中的Reduce的功能是什么?

    • 功能:实现分布式中合的功能
    • 实现
      • 默认启动1个ReduceTask进程来处理Shuffle输出的数据
      • 每个ReduceTask会实例化一个Reducer类的对象,调用reduce方法实现聚合
  5. MapReduce中的Output的功能是什么?

    • 功能:负责整个程序的输出
    • 实现:将上一步的KV保存到外部系统
  6. MapReduce开发过程中涉及的类及对应的方法?

    • Driver:作为程序入口
      • 规则:继承Configured 实现 Tool
      • 方法
        • run:构建、配置、提交MapReduce Job
        • main:程序入口,调用run方法
    • Input:实现数据读取
      • 规则:InputFormat
      • 默认:TextInputFormat
        • 读文件
        • K:行的偏移量
        • V:行的内容
    • Mapper:实现Map阶段的处理
      • 规则:继承Mapper
      • 重写:map
    • Reducer:实现Reduce阶段的处理
      • 规则:继承Reducer
      • 重写:reduce
    • Output:负责实现结果的保存
      • 规则:OutputFormat
      • 默认:TextOutputFormat
  7. MapReduce中的数据结构与数据类型是什么?

    • 结构:KV键值对
    • 类型:支持Hadoop序列化的类型
  8. 实现MapReduce模板的开发

  9. 反馈问题

    • MapReduce是纯cpu运算吗?有木有办法像cuda那样调用gpu并行运算实现显卡加速?
    • 每一个MapTask和ReduceTask进程中,为什么不是使用多线程的方式调用的map方法和reduce方法。
    • 这个很简单?内心狂叫你确定很简单?
    • 本地测试的时候,另外两个Mapper和Reducer类为什么还用public修饰,不是说public修饰的类名必须和文件名一致的嘛?

知识点02:课程目标

  1. MapReduce中使用问题
    • Reduce负载问题
      • reduce只有1个,如果负载过高,性能就会比较差
      • 可以启动多个Reduce
      • Map的所有数据如何分配给多个Reduce
      • |
      • 分区
    • 数据类型问题
      • MapReduce的数据结构是KV,数据类型只能用序列化类型
      • |
      • 问题:数据只能有两列,如果要处理的数据有多列怎么办?
      • |
      • 自定义数据类型:自己写JavaBean 实现序列化
  2. Shuffle过程:排序
    • 为什么要做排序?
    • 排序的规则?
    • 自定义排序?

知识点03:Task个数问题

  • 引入:Task的个数如何决定?Reduce个数什么时候会有多个?

  • 目标掌握MapReduce中Task个数的规则及配置

  • 路径

    • step1:Task个数的默认规则
    • step2:多个Reduce场景
    • step3:多个Reduce的配置
  • 实施

    • Task个数的默认规则

      • MapTask:由Split的分片个数决定
      • ReduceTask:默认只有1个,可以自己配置多个
    • 多个Reduce场景

      • 场景:单个ReduceTask处理的数据量过多,导致性能比较差,或者单机资源不足,程序失败
        在这里插入图片描述

      • 解决:配置多个ReduceTask

    在这里插入图片描述

    • 多个Reduce的配置

      • 方式一:动态指定

        job.setNumReduceTasks(2);//设置Reduce个数
        
      • 方式二:手动指定配置

        conf.set("mapreduce.job.reduces","2");
        

在这里插入图片描述

  • 小结

    • Task的个数如何决定?

      • MapTask:分片个数
      • ReduceTask:默认只有1个,可以指定多个
    • Reduce个数什么时候会有多个?

      • 当Reduce处理的数据量过多导致性能比较差,单机资源不足
    • 如何配置多个Reduce?

      job.setNumReduceTasks(N)
      

知识点04:MapReduce分区机制

  • 引入:多个Reduce的场景下,如何MapTask的数据会给哪个Reduce处理?

  • 目标掌握MapReduce中的分区机制

  • 路径

    • step1:分区机制
    • step2:分区规则
  • 实施

    • 分区机制

      • 每个Reduce就是一个分区

      • 功能:多个Reduce时,按照一定的规则将所有Map的数据分配给不同的Reduce

      • 阶段:Map输出后进入Shuffle阶段

        • 每一条Map阶段输出的数据都会调用分区器来计算自己属于哪个分区,归哪个Reduce进行处理

在这里插入图片描述
在这里插入图片描述

  • 分区规则

    • 本质:给数据打标签

    • 规则:默认的分区器调用的是HashPartitioner

      • Key的Hash值取余分区个数

        public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {
        
          public void configure(JobConf job) {}
        
          /** Use {@link Object#hashCode()} to partition. */
          //返回值为分区的编号
          public int getPartition(K2 key, V2 value,int numReduceTasks) {
            return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
          }
        }
        
    • 优缺点

      • 优点:相同Key的数据在同一个Reduce中
      • 缺点:数据分配不均衡
        • 如果所有Key的hash值取余的结果都是一个,所有的数据都由一个reduce处理,其他reduce就是空闲的,资源浪费了
  • 小结

    • 什么是分区机制?
      • 多个Reduce的情况下,根据分区机制的规则计算当前的Map输出的每条数据最终会被哪个Reduce进行处理
    • 分区的规则是什么?
      • Hash分区:根据K2的hash值取余分区的个数

知识点05:Shuffle自定义分区实现

  • 引入:如果工作中希望根据自己的规则实现分区怎么办?

  • 目标掌握MapReduce中自定义分区的开发规则及实现

  • 路径

    • step1:自定义分区的规则
    • step2:自定义分区的实现
  • 实施

    • 自定义分区的规则

      • 规则:继承Partitioner类,重写getPartition方法

      • 配置

        job.setPartitionerClass(HashPartitioner.class); //设置分区器,默认是Hash分区器
        
    • 自定义分区的实现

      • 需求:将浦东的二手房,单独由一个Reduce计算,其他地区进入另外一个reduce计算

      • 实现:两个Reduce,分区规则:浦东一个分区,其他在另一个分区

        • 自定义一个分区器

          package bigdata.itcast.cn.hadoop.mr.partition;
          
          import org.apache.hadoop.io.IntWritable;
          import org.apache.hadoop.io.Text;
          import org.apache.hadoop.mapreduce.Partitioner;
          
          /**
           * @ClassName UserPartition
           * @Description TODO 自定义分区器
           *              extends Partitioner<Text, IntWritable>
           *                  必须指定K2和V2的类型,重写getPartition方法必须指定类型
           * @Date 2021/4/27 11:06
           * @Create By     Frank
           */
          public class UserPartition extends Partitioner<Text, IntWritable> {
          
              /**
               * Map输出的每一条数据会调用一次
               * @param k2:key
               * @param v2:value
               * @param numPartitions:reduce个数
               * @return:分区的编号,0,1
               */
              public int getPartition(Text k2, IntWritable v2, int numPartitions) {
                  //获取当前的地区
                  String region = k2.toString();
                  //判断 这个地区是否是浦东
                  if("浦东".equals(region)){
                      return 0;
                  }else
                      return 1;
              }
          }
          
          
        • 代码中指定分区器

                  //Shuffle:配置Shuffle
                  job.setPartitionerClass(UserPartition.class); //设置分区器
          
                  //Reduce:配置Reduce
                  job.setNumReduceTasks(2); //设置ReduceTask的个数,默认为1
          
        • 结果
          在这里插入图片描述

  • 小结

    • 自定义分区的规则是什么?
      • 继承Partitioner,重写getPartition(K2,V2,reduce个数)

知识点06:MapReduce中的数据类型

  • 引入:MapReduce中的数据类型与Java中数据类型的区别?
  • 目标掌握MapReduce中数据类型的特点
    • 为什么不能使用Java中的类型?
    • MapReduce中自带了哪些类型?
    • MapReduce的类型是如何实现序列化和反序列化的?
  • 路径
    • step1:序列化与反序列化
    • step2:Hadoop中的自带数据类型
  • 实施
    • 序列化与反序列化
      • 定义:在Java实现对象的传递过程中需要使用序列化与反序列化
        • 数据传递:A -> 1 -> B
        • 对象传递:A ->Int a = 1 -> B
      • 序列化:将对象转换为字节
      • 反序列化:将字节解析为对象
    • Hadoop中的自带数据类型
      • 非String:xxxWritable
        • IntWritable
        • LongWritable
        • BooleanWritable
        • ……
      • String:Text
      • 怎么实现序列化的?
        • 都实现Writable接口
        • 重写方法
          • write:序列化方法
          • readFields:反序列化方法
  • 小结
    • MapReduce中的数据类型有哪些?
      • 非String:xxxxWritable
      • String:Text
      • 本质:将Java的类型封装成JavaBean,实现了序列化接口
    • 如何实现实现序列化与反序列化的?
      • 实现Writable
      • 重写write和readFields

知识点07:数据处理的问题及解决方案

  • 引入:MapReduce中只能传递KV,KV类型只能存储一列,如果要实现多列数据处理传输怎么办?

  • 目标了解MapReduce中数据处理的问题及解决方案

    • 怎么实现多列传输?
  • 路径

    • step1:数据处理的问题
    • step2:解决方案
  • 实施

    • 数据处理的问题

      • MapReduce中的数据传输只能以KV形式传输

      • MapReduce中的数据类型只能是Hadoop自带的类型

      • MapReduce中只能传递两列

      • 如果数据处理过程中需要传递多列怎么办?

      • 例如:MapReduce实现Wordcount时,最后结果中希望加上一列,单词的长度,结果如下:

        hadoop  6	4
        hbase   5	2
        hive    4	2
        spark   5	3
        
      • 如何实现?

    • 解决方案

      • 方案一:拼接字符串
      • 方案二:封装JavaBean
  • 小结

    • 怎么实现多列传输?
      • 拼接字符串:将多列通过Text拼接为1列,实现传输
      • 封装JavaBean:自定义一个JavaBean,在JavaBean中定义多个属性,按照Hadoop规则,实现Writable接口,就可以在Hadoop中使用

知识点08:方案一:拼接字符串

  • 目标实现通过拼接字符串解决多列传输及处理的问题

  • 实施

    • 代码

      package bigdata.itcast.cn.hadoop.mr.userbean;
      
      import org.apache.hadoop.conf.Configuration;
      import org.apache.hadoop.conf.Configured;
      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.TextInputFormat;
      import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
      import org.apache.hadoop.util.Tool;
      import org.apache.hadoop.util.ToolRunner;
      
      import java.io.IOException;
      
      /**
       * @ClassName WordCountDriver
       * @Description TODO 自定义开发程序实现Wordcount词频统计
       * @Date 2021/4/26 15:33
       * @Create By     Frank
       */
      public class WordCountConcatString extends Configured implements Tool {
          public int run(String[] args) throws Exception {
      
              //构建
              Job job = Job.getInstance(this.getConf(),"userwc");
              job.setJarByClass(WordCountConcatString.class);
      
              //配置
              job.setInputFormatClass(TextInputFormat.class);
              //使用程序的第一个参数作为输入
              TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt"));
      
              job.setMapperClass(WCMapper.class);
              job.setMapOutputKeyClass(Text.class);
              job.setMapOutputValueClass(IntWritable.class);
      
              job.setReducerClass(WCReducer.class);
              job.setOutputKeyClass(Text.class);
              job.setOutputValueClass(IntWritable.class);
      
      
              job.setOutputFormatClass(TextOutputFormat.class);
              //使用程序的第二个参数作为输出路径
              Path outputPath = new Path("datas/output/wc/output4");
              FileSystem fs = FileSystem.get(this.getConf());
              if(fs.exists(outputPath)){
                  fs.delete(outputPath,true);
              }
              TextOutputFormat.setOutputPath(job,outputPath);
      
              return job.waitForCompletion(true) ? 0 : -1;
          }
      
          public static void main(String[] args) throws Exception {
              Configuration conf = new Configuration();
              int status = ToolRunner.run(conf, new WordCountConcatString(), args);
              System.exit(status);
          }
      
      
          public static  class WCMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
              //输出的K2
              Text outputKey = new Text();
              //输出的V2
              IntWritable outputValue = new IntWritable(1);
      
              /**
               * 每条KV调用一次map
               * @param key:行的偏移量
               * @param value:行的内容
               * @param context
               * @throws IOException
               * @throws InterruptedException
               */
              @Override
              protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                  //将每行的内容分割得到每个单词
                  String[] words = value.toString().split("\\s+");
                  //迭代取出每个单词作为K2
                  for (String word : words) {
                      //将当前的单词作为K2
                      this.outputKey.set(word);
                      //将K2和V2传递到下一步
                      context.write(outputKey,outputValue);
                  }
              }
          }
      
      
          public static class WCReducer extends Reducer<Text, IntWritable,Text, IntWritable> {
      
              //输出K3:拼接单词和单词的长度
              Text outputKey = new Text();
              //输出V3
              IntWritable outputValue = new IntWritable();
      
              /**
               * 每一组调用一次
               * @param key:单词
               * @param values:所有相同单词对应的1
               * @param context
               * @throws IOException
               * @throws InterruptedException
               */
              @Override
              protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
                  int sum = 0;
                  for (IntWritable value : values) {
                      //取出当前单词所有 1,进行累加
                      sum += value.get();
                  }
                  //给K3赋值:单词和单词的长度
                  this.outputKey.set(key.toString()+"\t"+key.toString().length());
                  //给V3赋值
                  this.outputValue.set(sum);
                  //传递到下一步
                  context.write(this.outputKey,this.outputValue);
              }
          }
      
      }
      
      
      
    • 结果

在这里插入图片描述

  • 小结

    • 基本与Java拼接字符串方案一致
    • 优点:简单
    • 缺点:如果字符串过多,数据类型不一致,维护顺序麻烦,处理容易出错

知识点09:方案二:自定义数据类型封装JavaBean

  • 引入:如果涉及到多列数据处理或者数据传输,MapReduce自带的类型不能满足需求,拼接字符串也不方便,如何解决?

  • 目标掌握MapReduce中自定义数据类型的实现

  • 路径

    • step1:规则
    • step2:实现
  • 实施

    • 规则

      • 实现Writable接口:实现序列化与反序列化
      • 重写方法
        • write:序列化
        • readFields:反序列化
    • 实现

      • 需求:定义一个类型,存储一个String属性和一个Int属性,用于实现MapReduce中的数据传输

      • 实现

        • 自定义数据类型

          package bigdata.itcast.cn.hadoop.mr.userbean;
          
          import org.apache.hadoop.io.Writable;
          
          import java.io.DataInput;
          import java.io.DataOutput;
          import java.io.IOException;
          
          /**
           * @ClassName UserBean1
           * @Description TODO 自定义封装JavaBean,作为MapReduce数据传输的类型
           * @Date 2021/4/27 11:35
           * @Create By     Frank
           */
          public class UserBean1 implements Writable {
              //给定属性
              private String firstKey;
              private int secondKey;
          
              //构造
              public UserBean1(){}
          
              //get and set
          
              public String getFirstKey() {
                  return firstKey;
              }
          
              public void setFirstKey(String firstKey) {
                  this.firstKey = firstKey;
              }
          
              public int getSecondKey() {
                  return secondKey;
              }
          
              public void setSecondKey(int secondKey) {
                  this.secondKey = secondKey;
              }
          
              //toString
          
              @Override
              public String toString() {
                  return this.firstKey+"\t"+this.secondKey;
              }
          
              //序列化
              public void write(DataOutput out) throws IOException {
                  out.writeUTF(this.firstKey);//类型必须一致
                  out.writeInt(this.secondKey);
              }
          
              //反序列化
              public void readFields(DataInput in) throws IOException {
                  this.firstKey = in.readUTF(); //必须与序列化的顺序保持一致
                  this.secondKey = in.readInt();
              }
          }
          
          
          
        • 代码

          package bigdata.itcast.cn.hadoop.mr.userbean;
          
          import org.apache.hadoop.conf.Configuration;
          import org.apache.hadoop.conf.Configured;
          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.TextInputFormat;
          import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
          import org.apache.hadoop.util.Tool;
          import org.apache.hadoop.util.ToolRunner;
          
          import java.io.IOException;
          
          /**
           * @ClassName WordCountDriver
           * @Description TODO 自定义开发程序实现Wordcount词频统计
           * @Date 2021/4/26 15:33
           * @Create By     Frank
           */
          public class WordCountUserBean1 extends Configured implements Tool {
              public int run(String[] args) throws Exception {
          
                  //构建
                  Job job = Job.getInstance(this.getConf(),"userwc");
                  job.setJarByClass(WordCountUserBean1.class);
          
                  //配置
                  job.setInputFormatClass(TextInputFormat.class);
                  //使用程序的第一个参数作为输入
                  TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt"));
          
                  job.setMapperClass(WCMapper.class);
                  job.setMapOutputKeyClass(Text.class);
                  job.setMapOutputValueClass(IntWritable.class);
          
                  job.setReducerClass(WCReducer.class);
                  job.setOutputKeyClass(UserBean1.class);
                  job.setOutputValueClass(IntWritable.class);
          
          
                  job.setOutputFormatClass(TextOutputFormat.class);
                  //使用程序的第二个参数作为输出路径
                  Path outputPath = new Path("datas/output/wc/output5");
                  FileSystem fs = FileSystem.get(this.getConf());
                  if(fs.exists(outputPath)){
                      fs.delete(outputPath,true);
                  }
                  TextOutputFormat.setOutputPath(job,outputPath);
          
                  return job.waitForCompletion(true) ? 0 : -1;
              }
          
              public static void main(String[] args) throws Exception {
                  Configuration conf = new Configuration();
                  int status = ToolRunner.run(conf, new WordCountUserBean1(), args);
                  System.exit(status);
              }
          
          
              public static  class WCMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
                  //输出的K2
                  Text outputKey = new Text();
                  //输出的V2
                  IntWritable outputValue = new IntWritable(1);
          
                  /**
                   * 每条KV调用一次map
                   * @param key:行的偏移量
                   * @param value:行的内容
                   * @param context
                   * @throws IOException
                   * @throws InterruptedException
                   */
                  @Override
                  protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                      //将每行的内容分割得到每个单词
                      String[] words = value.toString().split("\\s+");
                      //迭代取出每个单词作为K2
                      for (String word : words) {
                          //将当前的单词作为K2
                          this.outputKey.set(word);
                          //将K2和V2传递到下一步
                          context.write(outputKey,outputValue);
                      }
                  }
              }
          
          
              public static class WCReducer extends Reducer<Text, IntWritable,UserBean1, IntWritable> {
          
                  //输出K3:拼接单词和单词的长度
                  UserBean1 outputKey = new UserBean1();
                  //输出V3
                  IntWritable outputValue = new IntWritable();
          
                  /**
                   * 每一组调用一次
                   * @param key:单词
                   * @param values:所有相同单词对应的1
                   * @param context
                   * @throws IOException
                   * @throws InterruptedException
                   */
                  @Override
                  protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
                      int sum = 0;
                      for (IntWritable value : values) {
                          //取出当前单词所有 1,进行累加
                          sum += value.get();
                      }
                      //给K3赋值:单词和单词的长度
                      this.outputKey.setFirstKey(key.toString());
                      this.outputKey.setSecondKey(key.toString().length());
                      //给V3赋值
                      this.outputValue.set(sum);
                      //传递到下一步
                      context.write(this.outputKey,this.outputValue);
                  }
              }
          
          }
          
          
          
        • 结果

在这里插入图片描述

  • 小结

    • MapReduce如何实现自定义数据类型?
      • 构建一个JavaBean,将每一列作为JavaBean中的属性
      • 必须实现Writable接口,重写write和readFields
        • 序列化:类型必须指定
        • 反序列化:顺序必须与序列化顺序一致

知识点10:MapReduce程序的分类

  • 引入:MapReduce如果实现没有分组聚合的场景是否需要Shuffle和Reduce呢?

  • 目标掌握MapReduce的程序的分类及应用场景

  • 路径

    • step1:五大阶段程序
    • step2:三大阶段程序
  • 实施

    • 五大阶段程序

      • 阶段:Input、Map、Shuffle、Reduce、Output
      • 性能:相对较慢
      • 应用:需要分组聚合和全局排序
        • 分组排序:Shuffle
        • 聚合:Reduce
        • 统计分析类的程序一般都是五大阶段的:多对一
          • 统计单词的个数
          • 统计二手房的个数
          • 统计二手房的平均单价
          • 统计二手房的最高单价
          • 统计最低单价
    • 三大阶段程序

      • 阶段:Input、Map、Output

      • 性能:相对较快

      • 应用:不需要分组聚合或者全局排序

        • 一般做ETL程序会用三大阶段:一对一

          18/Aug/2020:18:30:20   userid1			url1
          19/Aug/2020:18:30:20   userid2			url1
          10/Aug/2020:18:30:20   userid3			url1
          
          

          |

          2020-08-18 18:30:20   userid1			url1
          2020-08-19 18:30:20   userid2			url1
          2020-08-10 18:30:20   userid3			url1
          
          
  • 小结

    • MapReduce程序分为几类,各自的应用场景是什么?
      • 五大阶段:分组聚合或者全局排序
      • 三大阶段:ETL场景

知识点11:三大阶段的MapReduce程序

  • 目标实现MapReduce三大阶段的程序开发

  • 路径

    • step1:需求
    • step2:实现
  • 实施

    • 需求

      • 将WordCount程序中Map的结果进行输出,并增加一列单词的长度
    • 配置

      //如果没有Reduce,必须指定下面这句代码
              job.setNumReduceTasks(0);//不走shuffle和reduce过程
      
      
    • 实现

      package bigdata.itcast.cn.hadoop.mr.mapout;
      
      import bigdata.itcast.cn.hadoop.mr.userbean.UserBean1;
      import org.apache.hadoop.conf.Configuration;
      import org.apache.hadoop.conf.Configured;
      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.TextInputFormat;
      import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
      import org.apache.hadoop.util.Tool;
      import org.apache.hadoop.util.ToolRunner;
      
      import java.io.IOException;
      
      /**
       * @ClassName WordCountDriver
       * @Description TODO 自定义开发程序实现Wordcount词频统计
       * @Date 2021/4/26 15:33
       * @Create By     Frank
       */
      public class WordCountMapOutput extends Configured implements Tool {
          public int run(String[] args) throws Exception {
      
              //构建
              Job job = Job.getInstance(this.getConf(),"userwc");
              job.setJarByClass(WordCountMapOutput.class);
      
              //配置
              job.setInputFormatClass(TextInputFormat.class);
              //使用程序的第一个参数作为输入
              TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt"));
      
              job.setMapperClass(WCMapper.class);
              job.setMapOutputKeyClass(UserBean1.class);
              job.setMapOutputValueClass(IntWritable.class);
      
      //        job.setReducerClass(WCReducer.class);
      //        job.setOutputKeyClass(UserBean1.class);
      //        job.setOutputValueClass(IntWritable.class);
              //如果没有Reduce,必须指定下面这句代码
              job.setNumReduceTasks(0);//不走shuffle和reduce过程
      
      
              job.setOutputFormatClass(TextOutputFormat.class);
              //使用程序的第二个参数作为输出路径
              Path outputPath = new Path("datas/output/wc/output6");
              FileSystem fs = FileSystem.get(this.getConf());
              if(fs.exists(outputPath)){
                  fs.delete(outputPath,true);
              }
              TextOutputFormat.setOutputPath(job,outputPath);
      
              return job.waitForCompletion(true) ? 0 : -1;
          }
      
          public static void main(String[] args) throws Exception {
              Configuration conf = new Configuration();
              int status = ToolRunner.run(conf, new WordCountMapOutput(), args);
              System.exit(status);
          }
      
      
          public static  class WCMapper extends Mapper<LongWritable, Text,UserBean1, IntWritable> {
              //输出的K2
              UserBean1 outputKey = new UserBean1();
              //输出的V2
              IntWritable outputValue = new IntWritable(1);
      
              /**
               * 每条KV调用一次map
               * @param key:行的偏移量
               * @param value:行的内容
               * @param context
               * @throws IOException
               * @throws InterruptedException
               */
              @Override
              protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                  //将每行的内容分割得到每个单词
                  String[] words = value.toString().split("\\s+");
                  //迭代取出每个单词作为K2
                  for (String word : words) {
                      //将当前的单词作为K2
                      this.outputKey.setFirstKey(word);
                      this.outputKey.setSecondKey(word.length());
                      //将K2和V2传递到下一步
                      context.write(outputKey,outputValue);
                  }
              }
          }
      
      
          public static class WCReducer extends Reducer<Text, IntWritable,UserBean1, IntWritable> {
      
              //输出K3:拼接单词和单词的长度
              UserBean1 outputKey = new UserBean1();
              //输出V3
              IntWritable outputValue = new IntWritable();
      
              /**
               * 每一组调用一次
               * @param key:单词
               * @param values:所有相同单词对应的1
               * @param context
               * @throws IOException
               * @throws InterruptedException
               */
              @Override
              protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
                  int sum = 0;
                  for (IntWritable value : values) {
                      //取出当前单词所有 1,进行累加
                      sum += value.get();
                  }
                  //给K3赋值:单词和单词的长度
                  this.outputKey.setFirstKey(key.toString());
                  this.outputKey.setSecondKey(key.toString().length());
                  //给V3赋值
                  this.outputValue.set(sum);
                  //传递到下一步
                  context.write(this.outputKey,this.outputValue);
              }
          }
      
      }
      
      
      
    • 结果

在这里插入图片描述

  • 小结

    • 如何实现三大阶段的程序?
      • 将Shuffle和Reduce的定义注释掉
      • 设置ReduceTask个数为0

知识点12:了解MapReduce中的排序问题

  • 目标了解MapReduce中的排序问题

  • 路径

    • step1:需求
    • step2:实现
  • 实施

    • 需求

      • 将WordCount程序中Reduce的结果进行输出,并增加一列单词的长度

        hadoop  6	4
        hbase   5	2
        hive    4	2
        spark   5	3
        
        
    • 实现

    • 报错

在这里插入图片描述

- 现象:类转换异常
- 原因:Shuffle中需要做排序和分组,本质是做比较,底层会按照K2进行比较,将K2强转为比较器对象
  - 类必须实现比较器的方法才能转成功
  • 小结

    • 为什么会出现这个问题?
      • Shuffle需要做排序和分组:做比较
      • 要想做比较:必须实现比较器的接口实现比较器的类
      • 我们自定义的K2,没有实现比较器接口,所以无法强转为比较类型对象,无法实现排序或者分组

知识点13:自定义数据类型实现比较器接口

  • 目标实现自定义数据类型并实现排序接口

  • 路径

    • step1:规则
    • step2:实现
  • 实施

    • 规则
      • 实现WritableComparable接口
      • 重写compareTo方法
  • 实现

    package bigdata.itcast.cn.hadoop.mr.sort;
    
    import org.apache.hadoop.io.Writable;
    import org.apache.hadoop.io.WritableComparable;
    
    import java.io.DataInput;
    import java.io.DataOutput;
    import java.io.IOException;
    
    /**
     * @ClassName UserBean1
     * @Description TODO 自定义封装JavaBean,作为MapReduce数据传输的类型
     * @Date 2021/4/27 11:35
     * @Create By     Frank
     */
    public class UserBean2 implements WritableComparable<UserBean2> {
        //给定属性
        private String firstKey;
        private int secondKey;
    
        //构造
        public UserBean2(){}
    
        //get and set
    
        public String getFirstKey() {
            return firstKey;
        }
    
        public void setFirstKey(String firstKey) {
            this.firstKey = firstKey;
        }
    
        public int getSecondKey() {
            return secondKey;
        }
    
        public void setSecondKey(int secondKey) {
            this.secondKey = secondKey;
        }
    
        //toString
    
        @Override
        public String toString() {
            return this.firstKey+"\t"+this.secondKey;
        }
    
        //序列化
        public void write(DataOutput out) throws IOException {
            out.writeUTF(this.firstKey);//类型必须一致
            out.writeInt(this.secondKey);
        }
    
        //反序列化
        public void readFields(DataInput in) throws IOException {
            this.firstKey = in.readUTF(); //必须与序列化的顺序保持一致
            this.secondKey = in.readInt();
        }
    
        /**
         * 当这个类型作为K2经过shuffle时,需要调用该方法进行比较判断:排序和分组
         * @param o
         * @return
         */
        public int compareTo(UserBean2 o) {
            //先比较第一个属性
            int comp = this.getFirstKey().compareTo(o.getFirstKey());
            //如果第一个值,比较第二个值,用第二个属性的比较的结果作为最后的结果
            if(comp == 0){
                return Integer.valueOf(this.getSecondKey()).compareTo(Integer.valueOf(o.getSecondKey()));
            }
            return comp;
        }
    }
    
    
    
  • 小结

    • 如何实现自定义数据类型并实现排序接口?
      • 实现:WritableComparable
      • 重写:compareTo
    • 一般建议直接实现WritableComparable
      • 实现Writable:这个类型不能作为五大阶段中的K2

知识点14:MapReduce中的排序设计及规则

  • 引入:为什么在Driver类中要定义K2,V2,K3,V3的类型?

  • 目标掌握MapReduce中排序的设计及规则

  • 路径

    • step1:排序的设计目的
    • step2:排序的实现规则
  • 实施

    • 排序的设计目的

      • 排序的设计是为了加快Shuffle中分组的性能

        • 不排序

          hadoop	1
          hive	1
          spark	1
          hive	1
          hadoop	1
          spark	1
          
          
        • 排序

          hadoop	1
          hadoop	1
          hive	1
          hive	1
          spark	1
          spark	1
          
          
    • 排序的实现规则

      • step1:先调用排序器来实现排序
      • step2:如果没有排序器,会调用K2自带的compareTo方法来实现排序
  • 小结

    • 为什么要做排序?
      • 提高分组的性能
    • 如何做排序的?
      • step1:先调用排序器
      • step2:如果没有排序器,调用K2的compareTo方法

知识点15:自定义排序方案一:自定义数据类型

  • 目标实现自定义数据类型的排序

  • 路径

    • step1:需求分析
    • step2:代码实现
  • 实施

    • 需求分析

      • 对wordcount.txt进行词频统计,并输出结果如下

        spark	5	3
        hive	4	2
        hbase	5	2
        hadoop	6	4
        
        
    • 代码实现

      package bigdata.itcast.cn.hadoop.mr.sort;
      
      import org.apache.hadoop.io.Writable;
      import org.apache.hadoop.io.WritableComparable;
      
      import java.io.DataInput;
      import java.io.DataOutput;
      import java.io.IOException;
      
      /**
       * @ClassName UserBean1
       * @Description TODO 自定义封装JavaBean,作为MapReduce数据传输的类型
       * @Date 2021/4/27 11:35
       * @Create By     Frank
       */
      public class UserBean2 implements WritableComparable<UserBean2> {
          //给定属性
          private String firstKey;
          private int secondKey;
      
          //构造
          public UserBean2(){}
      
          //get and set
      
          public String getFirstKey() {
              return firstKey;
          }
      
          public void setFirstKey(String firstKey) {
              this.firstKey = firstKey;
          }
      
          public int getSecondKey() {
              return secondKey;
          }
      
          public void setSecondKey(int secondKey) {
              this.secondKey = secondKey;
          }
      
          //toString
      
          @Override
          public String toString() {
              return this.firstKey+"\t"+this.secondKey;
          }
      
          //序列化
          public void write(DataOutput out) throws IOException {
              out.writeUTF(this.firstKey);//类型必须一致
              out.writeInt(this.secondKey);
          }
      
          //反序列化
          public void readFields(DataInput in) throws IOException {
              this.firstKey = in.readUTF(); //必须与序列化的顺序保持一致
              this.secondKey = in.readInt();
          }
      
          /**
           * 当这个类型作为K2经过shuffle时,需要调用该方法进行比较判断:排序和分组
           * @param o
           * @return
           */
          public int compareTo(UserBean2 o) {
              //先比较第一个属性
              int comp = this.getFirstKey().compareTo(o.getFirstKey());
              //如果第一个值,比较第二个值,用第二个属性的比较的结果作为最后的结果
              if(comp == 0){
                  return Integer.valueOf(this.getSecondKey()).compareTo(Integer.valueOf(o.getSecondKey()));
              }
              //默认升序,如果要降序,添加负号即可
              return -comp;
          }
      }
      
      
      

在这里插入图片描述

  • 小结

    • 如何在自定义数据类型时进行排序?
      • 调用compareTo方法
      • 默认是升序,加负号就可以降序

知识点16:自定义排序方案二:自定义排序器

  • 目标实现自定义排序器排序

  • 路径

    • step1:自定义排序器的规则
    • step2:自定义排序器的实现
  • 实施

    • 自定义排序器的规则

      • 继承WritableComparator类
      • 重写compare方法
    • 自定义排序器的实现

      • 需求:实现词频统计,按照单词降序排序

      • 结果

        spark	3
        hive	2
        hbase	2
        hadoop	4
        
        
      • 实现自定义排序比较器

        package bigdata.itcast.cn.hadoop.mr.sort;
        
        import org.apache.hadoop.io.Text;
        import org.apache.hadoop.io.WritableComparable;
        import org.apache.hadoop.io.WritableComparator;
        
        /**
         * @ClassName UserSort
         * @Description TODO 用户自定义比较器
         * @Date 2021/4/27 15:03
         * @Create By     Frank
         */
        public class UserSort extends WritableComparator {
            //step1:注册
            public UserSort(){
                super(Text.class,true);
            }
        
            //step2:实现比较
        
            @Override
            public int compare(WritableComparable a, WritableComparable b) {
                //将两个比较器对象强转为要比较的Text类型
                Text t1 = (Text) a;
                Text t2 = (Text) b;
                //实现两个Text类型的比较,降序排序
                return -t1.toString().compareTo(t2.toString());
            }
        }
        
        
        
      • 代码

        package bigdata.itcast.cn.hadoop.mr.sort;
        
        import org.apache.hadoop.conf.Configuration;
        import org.apache.hadoop.conf.Configured;
        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.TextInputFormat;
        import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
        import org.apache.hadoop.util.Tool;
        import org.apache.hadoop.util.ToolRunner;
        
        import java.io.IOException;
        
        /**
         * @ClassName WordCountDriver
         * @Description TODO 自定义开发程序实现Wordcount词频统计
         * @Date 2021/4/26 15:33
         * @Create By     Frank
         */
        public class WordCountUserSort extends Configured implements Tool {
            public int run(String[] args) throws Exception {
        
                //构建
                Job job = Job.getInstance(this.getConf(),"userwc");
                job.setJarByClass(WordCountUserSort.class);
        
                //配置
                job.setInputFormatClass(TextInputFormat.class);
                //使用程序的第一个参数作为输入
                TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt"));
        
                job.setMapperClass(WCMapper.class);
                job.setMapOutputKeyClass(Text.class);
                job.setMapOutputValueClass(IntWritable.class);
        
                //配置Shuffle
                job.setSortComparatorClass(UserSort.class);//指定排序器
        
        
                job.setReducerClass(WCReducer.class);
                job.setOutputKeyClass(Text.class);
                job.setOutputValueClass(IntWritable.class);
        
        
                job.setOutputFormatClass(TextOutputFormat.class);
                //使用程序的第二个参数作为输出路径
                Path outputPath = new Path("datas/output/wc/output10");
                FileSystem fs = FileSystem.get(this.getConf());
                if(fs.exists(outputPath)){
                    fs.delete(outputPath,true);
                }
                TextOutputFormat.setOutputPath(job,outputPath);
        
                return job.waitForCompletion(true) ? 0 : -1;
            }
        
            public static void main(String[] args) throws Exception {
                Configuration conf = new Configuration();
                int status = ToolRunner.run(conf, new WordCountUserSort(), args);
                System.exit(status);
            }
        
        
            public static  class WCMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
                //输出的K2
                Text outputKey = new Text();
                //输出的V2
                IntWritable outputValue = new IntWritable(1);
        
                /**
                 * 每条KV调用一次map
                 * @param key:行的偏移量
                 * @param value:行的内容
                 * @param context
                 * @throws IOException
                 * @throws InterruptedException
                 */
                @Override
                protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                    //将每行的内容分割得到每个单词
                    String[] words = value.toString().split("\\s+");
                    //迭代取出每个单词作为K2
                    for (String word : words) {
                        //将当前的单词作为K2
                        this.outputKey.set(word);
                        //将K2和V2传递到下一步
                        context.write(outputKey,outputValue);
                    }
                }
            }
        
        
            public static  class WCReducer extends Reducer<Text, IntWritable,Text, IntWritable> {
        
                //输出K3
        //    Text outputKey = new Text();
                //输出V3
                IntWritable outputValue = new IntWritable();
        
                /**
                 * 每一组调用一次
                 * @param key:单词
                 * @param values:所有相同单词对应的1
                 * @param context
                 * @throws IOException
                 * @throws InterruptedException
                 */
                @Override
                protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
                    int sum = 0;
                    for (IntWritable value : values) {
                        //取出当前单词所有 1,进行累加
                        sum += value.get();
                    }
                    //给V3赋值
                    this.outputValue.set(sum);
                    //传递到下一步
                    context.write(key,this.outputValue);
                }
            }
        
        }
        
        
        
  • 小结

    • 如何实现自定义排序比较器?
      • 继承WritableComparator
      • 重写compare,代替compareTo

练习

  • 练习1:实现二次排序

    • 数据

      a	4
      b	0
      a	3
      c	6
      a	8
      b	7
      b	5
      c	3
      
      
    • 结果

      a	8
      a	4
      a	3
      b	7
      b	5
      b	0
      c	6
      c	3
      
      
  • 练习2:基于二手房数据统计每个地区二手房的个数,平均单价,最高单价,最低单价

    地区		个数		平均		最高		最低
    
    
  • 练习3:基于搜狗数据统计每种搜索词出现的次数

    • 演变的Wordcount程序

      搜索词		次数
      
      
  • 练习4:基于搜狗数据统计每个小时的UV

    小时			UV
    
    
    • UV:unique View:唯一访问用户数
      • 通过用户id唯一标识,一个用户id标识一个用户
    • 转换语义:统计每个小时用户id的个数

MapReduce的练习作业1

本题第二题用到reduce
其他三题可只用三大阶段(input-map-output)
现有如下数据
字段为: 门店名,营业额,开支额,年份

劲松店,600,350,2019年
劲松店,800,250,2020年
王府井店,1900,600,2020年
王府井店,2000,900,2019年
回龙观店,6700,1800,2020年
西单店,3000,1000,2019年
西单店,5000,1000,2020年
,3500,1000,2020年
牡丹园店,3800,1400,2020年
牡丹园店,2800,1300,2019年
西直门店,1500,900,2019年
太阳宫店,9000,3600,2019年
三里屯店,,1000,2020年
西直门店,3500,1000,2020年
太阳宫店,6000,4600,2020年
回龙观店,7500,2000,2019年

需求1:去除源文件中字段缺失的数据
本题只需要做一个判断是否为空白利用trim
String[] items = Value.toString().trim().split("’");

package exam1.exam1;


import exam2.FlowBean1;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;

import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;

    public class exam_drivers extends Configured implements Tool {
        public int run(String[] args) throws Exception {

            //构建
            Job job = Job.getInstance(this.getConf(),"userwc");
            job.setJarByClass(exam_drivers.class);

            //配置
            job.setInputFormatClass(TextInputFormat.class);
            //使用程序的第一个参数作为输入
            TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
            //配置map
            job.setMapperClass(WCMapper.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(NullWritable.class);
            //配置shuffle
            //job.setPartitionerClass(UserPartition.class);
            //配置reducer

            //job.setReducerClass(WCReducer.class);
            //job.setOutputKeyClass(Text.class);
            //job.setOutputValueClass(FlowBean1.class);
            job.setNumReduceTasks(0);

            job.setOutputFormatClass(TextOutputFormat.class);
            //使用程序的第二个参数作为输出路径
            Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\output5");
            FileSystem fs = FileSystem.get(this.getConf());
            if(fs.exists(outputPath)){
                fs.delete(outputPath,true);
            }
            TextOutputFormat.setOutputPath(job,outputPath);

            return job.waitForCompletion(true) ? 0 : -1;
        }

        public static void main(String[] args) throws Exception {
            Configuration conf = new Configuration();
            int status = ToolRunner.run(conf, new exam_drivers(), args);
            System.exit(status);
        }


        static class WCMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
            Text outputKey = new Text(); //K2
            FlowBean1 outputValue = new FlowBean1();  //V2

            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                String line = value.toString();
                String[] items = value.toString().trim().split(",");
                if (items.length != 4) {
                   return;
                }
                for (String item : items) {
                    if(item.trim().length() == 0){
                        return;
                    }
                }
                context.write(value, NullWritable.get());

            }
        }
    }

在这里插入图片描述

需求2:按照不同年份将营业数据拆分到不同的文件中
这一问是利用Shuffle分区输出结果
设置 job.setPartitionerClass(UserPartition.class)
job.setNumReduceTasks(2);
exam_drivers类

package exam1.exam2;


import exam2.FlowBean1;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;

    public class exam_drivers extends Configured implements Tool {
        public int run(String[] args) throws Exception {

            //构建
            Job job = Job.getInstance(this.getConf(),"userwc");
            job.setJarByClass(exam_drivers.class);

            //配置
            job.setInputFormatClass(TextInputFormat.class);
            //使用程序的第一个参数作为输入
            TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
            //配置map
            job.setMapperClass(WCMapper.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(NullWritable.class);

            //配置shuffle
            job.setPartitionerClass(UserPartition.class);


            //配置reducer
            job.setReducerClass(WCReducer.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(NullWritable.class);
            job.setNumReduceTasks(2);

            job.setOutputFormatClass(TextOutputFormat.class);
            //使用程序的第二个参数作为输出路径
            Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\output5");
            FileSystem fs = FileSystem.get(this.getConf());
            if(fs.exists(outputPath)){
                fs.delete(outputPath,true);
            }
            TextOutputFormat.setOutputPath(job,outputPath);

            return job.waitForCompletion(true) ? 0 : -1;
        }

        public static void main(String[] args) throws Exception {
            Configuration conf = new Configuration();
            int status = ToolRunner.run(conf, new exam_drivers(), args);
            System.exit(status);
        }

        static class WCMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
            NullWritable outputValue = NullWritable.get();
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
               String line = value.toString();
               String[] items = value.toString().trim().split(",");
               if (items.length != 4) {
                  return;
               }
               for (String item : items) {
                   if(item.trim().length() == 0){
                       return;
                   }
               }
                context.write(value,this.outputValue);

            }
        }

        private static class WCReducer extends Reducer<Text, NullWritable, Text, NullWritable> {

            @Override
            protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
                context.write(key,NullWritable.get());
            }
        }

    }

UserPartition类

package exam1.exam2;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;


public class UserPartition extends Partitioner<Text, NullWritable> {
    public int getPartition(Text text, NullWritable nullWritable, int i) {
        String year = text.toString().split(",")[3];
        if(year.startsWith("2019")) {
            return 0;
        }else {
            return 1;
        }
    }
}

输出的结果为两个目录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

需求3:对每一年的营业数据按照净盈利排序(营业额-开支额)
封装JavaBean
每一列分别为门店名,营业额,开支额,年份,净盈利
多加一个净盈利
写出compareTo方法以及write和readFields方法
compareTo方法是和自己做对比
切记 write和readField的顺序要一致
UserBean类

package exam1.exam3;

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class UserBean implements WritableComparable<UserBean> {
    private String sname; //门店
    private long totalInput;//总利润
    private long totalOutput;//总支出
    private String yearinfo;//年份
    private long input;//净利润

    public UserBean() {
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public long getTotalInput() {
        return totalInput;
    }

    public void setTotalInput(long totalInput) {
        this.totalInput = totalInput;
    }

    public long getTotalOutput() {
        return totalOutput;
    }

    public void setTotalOutput(long totalOutput) {
        this.totalOutput = totalOutput;
    }

    public String getYearinfo() {
        return yearinfo;
    }

    public void setYearinfo(String yearinfo) {
        this.yearinfo = yearinfo;
    }

    public long getInput() {
        return input;
    }

    public void setInput(long input) {
        this.input = input;
    }


    @Override
    public String toString() {
            return this.sname+"\t"+this.totalInput+"\t"+this.totalOutput+"\t"+this.yearinfo+"\t"+this.input;
    }

    public int compareTo(UserBean o) {
        //按照净利润降序排序
        return -Long.valueOf(this.getInput()).compareTo(Long.valueOf(o.getInput()));
    }

    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeUTF(this.sname);
        dataOutput.writeLong(this.totalInput);
        dataOutput.writeLong(this.totalOutput);
        dataOutput.writeUTF(this.yearinfo);
        dataOutput.writeLong(this.input);
    }

    public void readFields(DataInput dataInput) throws IOException {
        this.sname = dataInput.readUTF();
        this.totalInput = dataInput.readLong();
        this.totalOutput = dataInput.readLong();
        this.yearinfo = dataInput.readUTF();
        this.input = dataInput.readLong();
    }
}

exam_drivers类

package exam1.exam3;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;

public class exam_drivers extends Configured implements Tool {
    public int run(String[] args) throws Exception {

        //构建
        Job job = Job.getInstance(this.getConf(),"userwc");
        job.setJarByClass(exam_drivers.class);

        //配置
        job.setInputFormatClass(TextInputFormat.class);
        //使用程序的第一个参数作为输入
        TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
        //配置map
        job.setMapperClass(WCMapper.class);
        job.setMapOutputKeyClass(UserBean.class);
        job.setMapOutputValueClass(NullWritable.class);

        //配置shuffle
        //job.setPartitionerClass(UserPartition.class);

        //配置reducer
        //job.setReducerClass(WCReducer.class);
        //job.setOutputKeyClass(Text.class);
        //job.setOutputValueClass(NullWritable.class);
        //job.setNumReduceTasks(2);

        job.setOutputFormatClass(TextOutputFormat.class);
        //使用程序的第二个参数作为输出路径
        Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\output6");
        FileSystem fs = FileSystem.get(this.getConf());
        if(fs.exists(outputPath)){
            fs.delete(outputPath,true);
        }
        TextOutputFormat.setOutputPath(job,outputPath);

        return job.waitForCompletion(true) ? 0 : -1;
    }

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        int status = ToolRunner.run(conf, new exam_drivers(), args);
        System.exit(status);
    }

    public static class WCMapper extends Mapper<LongWritable, Text, UserBean, NullWritable> {
        UserBean outputKey = new UserBean();
        NullWritable outputValue = NullWritable.get();
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
           String line = value.toString();
           String[] items = value.toString().trim().split(",");
           if (items.length != 4) {
              return;
           }
           for (String item : items) {
               if(item.trim().length() == 0){
                   return;
               }
           }

           String sname = items[0];
           long totalInput = Long.parseLong(items[1]);
           long totalOutput = Long.parseLong(items[2]);
           String Yearinfo = items[3];
           long input = totalInput - totalOutput;

           this.outputKey.setSname(sname);
           this.outputKey.setTotalInput(totalInput);
           this.outputKey.setTotalOutput(totalOutput);
           this.outputKey.setYearinfo(Yearinfo);
           this.outputKey.setInput(input);
//
        context.write(this.outputKey,this.outputValue);
//
        }
    }


    private static class WCReducer extends Reducer<Text, NullWritable, Text, NullWritable> {

        @Override
        protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            context.write(key,NullWritable.get());
        }
    }

}

输出结果
在这里插入图片描述

需求4:要求最后输出到文件的数据字段之间以‘\t’分割,后边加两个描述字段:净盈利额、盈利或者亏损标记

如:

 王府井店       1900       600   2020年   1300     盈利

  劲松店          800        950   2020年   -150     亏损

这个只需要在上题的Value中加上一个字段判断输出即可

package exam1.exam4;

import exam1.exam3.UserBean;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;

public class exam_drivers extends Configured implements Tool {
    public int run(String[] args) throws Exception {

        //构建
        Job job = Job.getInstance(this.getConf(),"userwc");
        job.setJarByClass(exam_drivers.class);

        //配置
        job.setInputFormatClass(TextInputFormat.class);
        //使用程序的第一个参数作为输入
        TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
        //配置map
        job.setMapperClass(WCMapper.class);
        job.setMapOutputKeyClass(UserBean.class);
        job.setMapOutputValueClass(Text.class);

        //配置shuffle
        //job.setPartitionerClass(UserPartition.class);

        //配置reducer
        //job.setReducerClass(WCReducer.class);
        //job.setOutputKeyClass(Text.class);
        //job.setOutputValueClass(NullWritable.class);
        //job.setNumReduceTasks(2);

        job.setOutputFormatClass(TextOutputFormat.class);
        //使用程序的第二个参数作为输出路径
        Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\output6");
        FileSystem fs = FileSystem.get(this.getConf());
        if(fs.exists(outputPath)){
            fs.delete(outputPath,true);
        }
        TextOutputFormat.setOutputPath(job,outputPath);

        return job.waitForCompletion(true) ? 0 : -1;
    }

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        int status = ToolRunner.run(conf, new exam_drivers(), args);
        System.exit(status);
    }

    public static class WCMapper extends Mapper<LongWritable, Text, UserBean, Text> {
        UserBean outputKey = new UserBean();
        Text outputValue = new Text();
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
           String line = value.toString();
           String[] items = value.toString().trim().split(",");
           if (items.length != 4) {
              return;
           }
           for (String item : items) {
               if(item.trim().length() == 0){
                   return;
               }
           }

           String sname = items[0];
           long totalInput = Long.parseLong(items[1]);
           long totalOutput = Long.parseLong(items[2]);
           String Yearinfo = items[3];
           long input = totalInput - totalOutput;

           this.outputKey.setSname(sname);
           this.outputKey.setTotalInput(totalInput);
           this.outputKey.setTotalOutput(totalOutput);
           this.outputKey.setYearinfo(Yearinfo);
           this.outputKey.setInput(input);

           if(input > 0 ){
               this.outputValue.set("盈利");
               context.write(this.outputKey,this.outputValue);
           }else if (input < 0 ){
               this.outputValue.set("亏损");
               context.write(this.outputKey,this.outputValue);
           }


        }
    }


    private static class WCReducer extends Reducer<Text, NullWritable, Text, NullWritable> {

        @Override
        protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            context.write(key,NullWritable.get());
        }
    }

}

输出结果:

附录一:MapReduce编程依赖

<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-mapreduce-client-core</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
    </dependency>
</dependencies>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值