【Hadoop】WordCount源码分析

MapReduce

MapReduce是一种可用于数据处理的编程模型。它的任务过程分为两个处理阶段: map 阶段和 reduce 阶段。每阶段都以 键-值对 作为输入和输出,其类型由我们按需选择。我们还需要写两个函数: map 函数和 reduce 函数。

map 函数由Mapper类来表示,后者声明一个抽象的 map() 方法。Mapper类是一个泛型类型,它有四个形参类型,分别指定 map 函数的输入键、输入值、输出键、输出值的类型。

同样, reduce 函数也有四个形式参数类型用于指定输入和输出类型。 reduce 函数的输入类型必须匹配 map 函数的输出类型。


WordCount单词统计

首先有这么一个文件,文件内容如下:

hello world hello java  
hello hadoop

那么hadoop是怎么做单词统计的呢?我们用步骤来描述下:

  • 第一步:读取这个文件,按行来将这个文件每一行的单词给拆分出来,然后形成很多key/value的结果,处理完就是这样
    <hello,1>
    <world,1>
    <hello,1>
    <java,1>
    <hello,1>
    <hadoop,1>
  • 第二步:排序
    排序完会变成这样的结果
    <hadoop,1>
    <hello,1>
    <hello,1>
    <hello,1>
    <java,1>
    <world,1>
  • 第三步:合并
    合并后的结果如下
    <hadoop,1>
    <hello,1,1,1>
    <java,1>
    <world,1>
  • 第四步:汇聚结果
    <hadoop,1>
    <hello,3>
    <java,1>
    <world,1>

第二步和第三步是hadoop框架帮助我们完成的,我们实际上需要写代码的地方是第一步和第四步。 第一步对应的就是Map的过程,第四步对应的是Reduce的过程。


WordCount源码分析

import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.conf.Configuration;
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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WordCount {
   

  // TokenizerMapper作为Map阶段,需要继承Mapper,并重写map()函数
  public static class TokenizerMapper 
       extends Mapper<Object, Text, Text, IntWritable>{
   
      //这个泛型声明告诉MapReduce框架,Mapper的输入是不加限制的通用对象和文本,而输出是文本和整数
    
    private final static IntWritable one = new IntWritable(1);//IntWritable 是 Hadoop 提供的用于表示整数的数据类型。这里是为每个单词设置一个计数,表示单词在文本中出现的次数
    private Text word = new Text();
      
    public void map(Object key, Text value, Context context
                    ) throws IOException, InterruptedException {
   
      
      // 用StringTokenizer作为分词器,对value进行分词
      StringTokenizer itr = new StringTokenizer(value.toString());//默认情况下使用空格作为分隔符。
      
      // 遍历分词后结果
      while (itr.hasMoreTokens()) {
   
    	  
    	// itr.nextToken() 逐个获取单词。每个String类型的单词都会被设置到 Text 类型的 word 变量中
        word.set(itr.nextToken());
        // 将(word,1),即(Text,IntWritable)写入上下文context,供后续Reduce阶段使用
        context.write(word, one);
      }
    }
  }
  
  // IntSumReducer作为Reduce阶段,需要继承Reducer,并重写reduce()函数
  public static class IntSumReducer 
       extends Reducer<Text,IntWritable,Text,IntWritable> {
   
    private IntWritable result = new IntWritable();

    //reduce方法对每个键(单词)的值列表进行迭代,累加计算单词出现的总次数,并将结果输出为(word, total_count)。
    public void reduce(Text key, Iterable<IntWritable> values, 
                       Context context
                       ) throws IOException, InterruptedException {
   
      int sum = 0;
      // 遍历map阶段输出结果中的values中每个val,累加至sum
      for (IntWritable val : values) {
   
        sum += val.get();
      }
      
      // 将sum设置入IntWritable类型result
      result.set(sum);
      
      // 通过上下文context的write()方法,输出结果(key, result),即(Text,IntWritable)
      context.write(key, result);
    }
  }

  public static void main(String[] args) throws Exception {
   
    // 加载hadoop配置
	Configuration conf = new Configuration();
    
	// 校验命令行输入参数,确保用户提供了输入路径和输出路径
	if (args.length < 2) {
   
      System.err.println("Usage: wordcount <in> [<in>...] <out>");//要求用户提供输入路径(可能是多个),然后一个输出路径。
      System.exit(2);//在这里,退出码是 2,通常表示程序由于错误的使用方式而被终止
    }
	
	// 构造一个Job实例job,并命名为"word count"
    Job job = new Job(conf, "word count");
    
    // 设置jar,指定运行该作业的Jar文件。Hadoop利用方法中的类来查找包含它的JAR文件,进而找到相关的JAR文件。
    job.setJarByClass(WordCount.class);
    
    // 设置Mapper
    job.setMapperClass(TokenizerMapper.class);
    // 设置Combiner
    job.setCombinerClass(IntSumReducer.class);
    // 设置Reducer
    job.setReducerClass(IntSumReducer.class);
    // 设置OutputKey
    job.setOutputKeyClass(Text.class);
    // 设置OutputValue
    job.setOutputValueClass(IntWritable.class);
    
    // 添加输入路径。通过循环,将所有的输入路径添加到作业的配置中
    for (int i = 0; i < args.length - 1; ++i) {
   
      FileInputFormat.addInputPath(job, new Path(args[i]));
    }
    
    // 添加输出路径。通常是HDFS上的一个目录
    FileOutputFormat.setOutputPath(job,
      new Path(args[args.length - 1]));
    
    // 等待作业job运行完成并退出
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}

参考

https://juejin.cn/post/6844903942183190541

相关推荐

  1. SDWebImage分析

    2023-12-16 07:34:02       11 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-16 07:34:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-16 07:34:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-16 07:34:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-16 07:34:02       18 阅读

热门阅读

  1. Spring Boot中实现邮件推送

    2023-12-16 07:34:02       34 阅读
  2. 飞天使-实际运用安装rabbitmq

    2023-12-16 07:34:02       37 阅读
  3. 单片机Freertos入门(二)任务调度的介绍

    2023-12-16 07:34:02       32 阅读
  4. Stable Diffusion的数学原理

    2023-12-16 07:34:02       32 阅读
  5. QT 记录

    2023-12-16 07:34:02       44 阅读
  6. Kafka Avro序列化之一:使用自定义序列化

    2023-12-16 07:34:02       45 阅读
  7. isRef、unRef、toRef、toRefs、shallowRef

    2023-12-16 07:34:02       37 阅读
  8. g++/git/vim相关学习笔记

    2023-12-16 07:34:02       37 阅读
  9. linux定时任务

    2023-12-16 07:34:02       38 阅读
  10. 电学基础名词

    2023-12-16 07:34:02       37 阅读
  11. html 基础学习笔记

    2023-12-16 07:34:02       29 阅读
  12. Lua 模仿C++类

    2023-12-16 07:34:02       40 阅读