wordcount进化:统计总共单词个数+ 自定义Combiner、 Partioner 类

task1:WordCount统计总共单词个数

任务描述

wordcount上一次的任务是统计每个单词出现的次数,现在的功能要修改成统计文本中一共有多少个不同的单词。

解决思想

两个mapreduce的job串联运行,第一个job进行分词和词频统计,第二个job统计共有多少词,job2的map把每行都输出成<sum,1>,reduce任务不做修改,结果即为所求单词总数。

具体实现代码

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;
import org.apache.hadoop.util.GenericOptionsParser;

//启动mr的driver类
public class WordCount2 {

    //JOB1的map类,实现map函数
    public static class TokenizerMapper extends
            Mapper<Object, Text, Text, IntWritable> {
        //暂存每个传过来的词频计数,均为1,省掉重复申请空间
        private final static IntWritable one = new IntWritable(1);
        //暂存每个传过来的词的值,省掉重复申请空间
        private Text word = new Text();

        //核心map方法的具体实现,逐个<key,value>对去处理. 在词频统计操作中,value是文本中的一行,key是文本中的行号
        public void map(Object key, Text value, Context context)
                throws IOException, InterruptedException {
            //用每行的字符串值初始化StringTokenizer
            StringTokenizer itr = new StringTokenizer(value.toString());
            //循环取得每个空白符分隔出来的每个元素(单词)
            while (itr.hasMoreTokens()) {
                //将取得出的每个元素放到word Text对象中
                word.set(itr.nextToken());
                //通过context对象,将map的输出逐个输出
                context.write(word, one);
            }
        }
    }

    //JOB1,JOB2可以公用的reduce类,实现reduce函数
    public static class IntSumReducer extends
            Reducer<Text, IntWritable, Text, IntWritable> {
        private IntWritable result = new IntWritable();

        //核心reduce方法的具体实现,逐个<key,List(v1,v2)>去处理
        public void reduce(Text key, Iterable<IntWritable> values,
                           Context context) throws IOException, InterruptedException {
            //暂存每个key组中计算总和
            int sum = 0;
            //加强型for,依次获取迭代器中的每个元素值,即为一个一个的词频数值
            for (IntWritable val : values) {
                //将key组中的每个词频数值sum到一起
                sum += val.get();
            }
            //将该key组sum完成的值放到result IntWritable中,使可以序列化输出
            result.set(sum);
            //将计算结果逐条输出
            context.write(key, result);
        }
    }

    //JOB2的map类,实现map函数
    public static class TokenizerMapper2 extends
            Mapper<Object, Text, Text, IntWritable> {
        //暂存每个传过来的词频计数,均为1,省掉重复申请空间
        private final static IntWritable one = new IntWritable(1);
        //暂存每个传过来的词的值,省掉重复申请空间
        private Text word = new Text();

        //核心map方法的具体实现,逐个<key,value>对去处理. 在词频统计操作中,value是文本中的一行,key是文本中的行号
        public void map(Object key, Text value, Context context)
                throws IOException, InterruptedException {
            //用每行的字符串值初始化StringTokenizer
            //主要是更改了这个地方,把分隔符唯一指定成换行符
                word.set("sum");
                //通过context对象,将map的输出逐个输出
                context.write(word, one);
        }
    }

    //启动mr的driver方法
    public static void main(String[] args) throws Exception {
        //得到集群配置参数
        Configuration conf = new Configuration();
        String[] otherArgs = (new GenericOptionsParser(conf,args)).getRemainingArgs();
        if(otherArgs.length<2){
            System.out.println("缺少变量:wordcount<in>[<in>...]<out>");
            System.exit(2);
        }
        ///输入格式要求为:n个输入,1个job1的输出,1个job2的输出(最终输出)
        第一个job
        //设置到本次的job实例中
        Job job1 = Job.getInstance(conf, "张驰のWordCount");
        //指定本次执行的主类是WordCount
        job1.setJarByClass(WordCount.class);
        //指定map类
        job1.setMapperClass(TokenizerMapper.class);
        //指定combiner类,要么不指定,如果指定,一般与reducer类相同
        job1.setCombinerClass(IntSumReducer.class);
        //指定reducer类
        job1.setReducerClass(IntSumReducer.class);
//        //指定job输出的key和value的类型,如果map和reduce输出类型不完全相同,需要重新设置map的output的key和value的class类型
        job1.setOutputKeyClass(Text.class);
        job1.setOutputValueClass(IntWritable.class);
        //指定输入数据的路径
        for(int i=0;i<otherArgs.length-2;++i){
            FileInputFormat.addInputPath(job1,new Path(otherArgs[i]));
        }
        //指定输出路径,并要求该输出路径一定是不存在的
        FileOutputFormat.setOutputPath(job1, new Path(otherArgs[otherArgs.length-1]));


        //第二个job,用来统计有多少个单词
        Job job2 = Job.getInstance(conf, "张驰のWordCount~part2");
        //指定本次执行的主类是WordCount
        job2.setJarByClass(WordCount.class);
        //指定map类
        job2.setMapperClass(TokenizerMapper2.class);
        //指定combiner类,要么不指定,如果指定,一般与reducer类相同
        job2.setCombinerClass(IntSumReducer.class);
        //指定reducer类
        job2.setReducerClass(IntSumReducer.class);
            //指定job输出的key和value的类型,如果map和reduce输出类型不完全相同,需要重新设置map的output的key和value的class类型
        job2.setOutputKeyClass(Text.class);
        job2.setOutputValueClass(IntWritable.class);
        //指定输入数据的路径
        FileInputFormat.addInputPath(job2,new Path(otherArgs[otherArgs.length-1]));
        //指定输出路径,并要求该输出路径一定是不存在的
        FileOutputFormat.setOutputPath(job2, new Path(otherArgs[otherArgs.length-2]));



        //指定job执行模式,等待任务执行完成后,提交任务的客户端才会退出!
          if (job1.waitForCompletion(true)) {



              System.exit(job2.waitForCompletion(true) ? 0 : 1);
          }
    }
}

操作流程

  1. 词频统计的基本流程一样;基础操作参考林子雨
  2. 第一个MR输出到output文件夹,第二个输出到final文件夹
    在这里插入图片描述
    在这里插入图片描述

  1. 第一个map任务指定路径时,记得修改i<length-2,因为这次我们要后两个文件夹都是输出文件夹了;(106行)
  2. 根据这篇博客 实例理解MR串行任务修改了第二个map的代码才可以成功运行,但仍然没有搞清楚错误的原因,个人猜测应该是第一个MR输出的不是正常的文本文件,而是MR格式的,直接可以修改key和value,待更新;(71-78行)

task2:自定义Combiner、 Partioner 类

自定义Combiner

  1. WordCount中设置Combiner类时直接指定reduce类纯属巧合。(job.setCombinerClass(IntSumReducer.class);
  2. 实际上,Combiner 只适合于在对 map 函数的输出进行合并之后并不会影响最终结果的任务,比如求和、取最大和最小值,Reduce却可以做的更多。Reducer 可以产生新类型的<key, value>。
  3. WordCount中Combiner类与reduce类所做的工作是一样的,所以无需重新定义。

自定义Partioner类

  1. shuffle过程中,MapReduce 默认根据 Hash 方法来将 map 函数的输出分成多个分区,Hash的实现代码如下:
public class HashPartitioner<K, V> extends Partitioner<K, V> {
	public int getPartition(K key, V value, int numReduceTasks) {
		return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
  1. 这个代码用来计算该键值对所属的 reduce 任务,reduce任务个数通过job.setNumReducerTasks(3);来设置。
  2. 实际操作中,如果想要根据需要将不同的任务交由不同的reduce任务处理,就需要自定义Partioner。课堂资料给了一个例子,首字母小于K和大于K的分成两个reduce任务,输出到不同文件夹,则实现方法为:
//---设置 reduce 任务个数
job.setNumReducerTasks(2);
//---自定义partioner
public class MyPartitioner<K, V> extends Partitioner<K, V> {
	public int getPartition(K key, V value, int numReduceTasks) {
		return key.toString.charAt(0)<”k”? 0:1; }
//---设置 Partitioner
job.setPartitionerClass(MyPartitioner.class);
  1. 有一点需要在自定义 Partitioner 类时注意:根据自定义的 Partitioner 所产生的分区个数要与设置的 reduce 任务个数一致。

参考资料

  1. 博客 关于wordcount过程的课堂笔记
  2. 大数据平台课第十周课堂资料 提取码:as12
  3. 博客 实例理解MR串行任务(解决了很大的问题)
  4. 博客 MR串行处理
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值