WordCount 案例实操

WordCount 案例实操

一、案例需求

1、需求:在给定的文本文件中统计输出每一个单词的总次数
在这里插入图片描述
2、期望输出数据
atguigu 2
banzhang 1
cls 2
hadoop 1
jiao 1
ss 2
xue 1

二、案例分析

按照MapReduce 编程规范,分别编写 Mapper,Reducer,Driver,如下图所示
在这里插入图片描述
Mapper 只负责切和分:
Mapper首先传入进来是,Text 类型,但是我们对IntWritable类型的数据操作不熟悉,所以把它再转换为String类型的进行操作,读取行数据,然后根据空格将这一行的数据进行切分,成两个单词,有空格的才切哈,没得空格就不切。然后将单词输出为<单词,1>,
Reducer 负责汇总:
Rducer 阶段拿到的数据就是,刚刚Map阶段切分好的数据,汇总各个key的个数,比如以 atguigukey的单词,他们就会进入到同一个Reduce方法里面去,然后会将同一个Reduce方法里面的单词,进行汇总,就可以得到这个单词的次数
Driver 运行程序提交作业: 这是固定的程序,基本所有的程序都这么写
获取配置信息,获取job对象实例,指定本程序的jar所在的本地路经,关联Mapper/Reducer业务类,指定Mapper输出类型的KV值类型,指定最终输出的数据的KV类型,指定job的输入原始文件所在目录,指定job的输出结果所在目录,然后最后提交作业

三、代码实现

1、Map阶段代码

map阶段要做的事就是把准备好的数据,输入进去,然后按行读取,然后使用split空格切分单词,然后同一个key的单词返回到同一个数组里面,比如 [atguigu,atguigu],然后再创建一个,返回值的对象变量v,然后使用context.write方法,将他们写入进去,就成<atguigu,1> <atguigu,1>这样KV键值对的形式了,然后map阶段的输出的数据,就是下一个Reduce阶段的输入的数据,值的类型都是一样的

package com.aex.mr;

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;

//map阶段
//KEYIN 第一个参数,输入数据的类型 行偏移量 0-19 然后第二行就是 20-25 第三行26-35 会有一点偏差
//VALAUEIN 第二个参数 输入数据的value Text 也就是String类型,因为读取进来的单词全是文本类型
//KEYOUT 输出数据的类型是KV atguigu,1 ss,1是这个样子的,所以应该设置为 Text IntWritable
//VALUEOUT 输出的数据的value类型 最后这两个是作为Reduce阶段的输入,这两个写什么Reduce的输入类型就写什么
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> { //第一个泛型是数据的类型,
    Text k = new Text();
    IntWritable v = new IntWritable(1);
    @Override  //每次key/value都会调用一次,输入的切片,必须要重写这个map方法
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //LongWritable kye 和Mapper类的LongWritable对应,是行偏移量。Text value和Text对应是输入数据的类型值
        //atguigu atguigu 一次只读取一行数据,第一次是第一行,就把这个atguigu读进来了
        //ss ss
        // 1 获取一行,他是Text类型,我们的给他转换成String类型的
        String line = value.toString(); //我们吧这个value使用tostring方法转换成string类型
        // 2 然后中间是有空格的,我们得根据空格进行切割单词
        String[] words = line.split(" ");//直接使用split方法进行切割,中间直接是空格,
        //然后切割完的结果返回一个数组,比如数组第一个就是第一个atguigu 然后数组第二个就是第二个atguigu
        // 3 循环写出
        for (String word:words){ //循环遍历这个数组,
            //atguigu 因为这个对象要是写在循环里的话每次都要创造一个对象有点耗内存,所以写在外面当全局变量一样的,但是我不删,注释了放在这里容易看懂
            //Text k = new Text(); //因为下面的context.write方法写入,然后key是Text类型的,所以new一个新的变量对象
            k.set(word); //然后把这个数组放进去.atguigu atguigu
            //1
            //IntWritable v = new IntWritable(); //值value是IntWritable类型的,也要new 一个新的变量对象,然后把他们两个传入进去就行了
            //v.set(1); //k是设置的单词。那么v就是1 然好map阶段的任务完成,数据输出变成atguigu,1 atguigu 1
            context.write(k,v); //使用context.write方法写出去,两个参数key,value。以单词为key,单词的数量为value
        }
    }
}

2、Reducer阶段代码

Reducer阶段要做的事就是,将两个 atguigu,1 gatguigu,1 变成atguigu,2 将会发现这个键是一样的,只是值发生了变化,值经过迭代器,for循环,进行了累加,然后最后用context.write() 方法把键值写出去,这个值的类型经过累加是int 类型的所以得转换回来成IntWriteable类型的,然后就可以写出去了 Reducer 阶段完成

package com.aex.mr;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
//一样的的Reduce 也要继承他的父类 四个参数是输入的键值的类型和输出的键值的类型
//KEYIN,VALUEIN map阶段输出的key和value
//需要输出的样子依然是单词为key,单词的个数为value <atguigu,2> 所以还是Text,IntWritable
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
    @Override     //第一个对应输入的键  第二个对于输入的值封装在value里,然后把它放进迭代器里面进行循环遍历,进行累加,atguigu,1 atguigu,1 他有两个 就成了atguigu,2
    protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
        //atguigu,1
        //atguigu,1
        int sum = 0; //定义一个变量用来计数
        // 1 累加求和
        for(IntWritable value:values){ //还是遍历这个封装了值的value
            sum += value.get();  //但是这个sum是int类型,这个value是IntWriteable类型,所以要.get,get返回的就是int类型
        }
        IntWritable v = new IntWritable();
        v.set(sum);
        // 写出  这里面是输出的键值,键还是跟输入的key是一样的都是atguigu,值变了值是累加的sum
        //输出变成了, atguigu,2 所以要把sum放进去,但是sum是int类型的,上面是IntWriteable类型的所以要
        //转换一下new 一个IntWritable对象变量v,然后用set方法给sum传进去
         //atguigu,2 最终的结果
        context.write(key,v); //一样的使用context.write写出,里面是key,value
        
        
    }
}

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

3、Driver 阶段代码

Driver 完全就是固定的格式,无论些什么基本上都是不变的,都是那些代码,大概分为下面几个步骤:

  1. 创建系配置对象 Configation
  2. 设置数据节点主机名属性 conf.set() 这个很重要一定要设置
  3. 设置jar包的存放路径 这个不设置jar包无法在linux上面运行
  4. 关联Mapper类和Reducer 类,将他们与job对象关联起来产生关系
  5. 设置map阶段输出数据的key和value的类型
  6. 设置最终数据输出的key和value的类型
  7. 设置输入路径和输出路径
  8. 给作业添加输入目录(允许有多个)
  9. 给作业设置输出目录(只有一个)
  10. 提交job job.waitForCompletion(true)
  11. 然后最后就是将输出的结果打印在控制台上,这一步有没有都无所谓因为把jar包放在linux上面跑是一样的,只是这样方便一点
package com.aex.mr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

//这个就测试类不需要继承任何的类了
public class WordCountDriver {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException, URISyntaxException {
        // 1 获取job对象
        Configuration conf = new Configuration(); //先创建配置对象
        // 设置数据节点主机名属性
        conf.set("dfs.client.use.datanode.hostname", "true"); //这个重中之重,一定要设置,不然汇报I/O错误
        Job job = Job.getInstance(conf); //然后把配置对象放在job对象的getInstance里面
        // 2 设置jar存放路径
        job.setJarByClass(WordCountDriver.class); //这个不搞就不能吧包放在linux上面跑
        // 3 关联Mapper和Reduce类
        job.setMapperClass(WordCountMapper.class); //让Mapper阶段和Reduce阶段跟job产生联系
        job.setReducerClass(WordCountReducer.class);
        // 4 设置map阶段输出数据的key和value类型
        job.setMapOutputKeyClass(Text.class); //分别是Text和IntWritable
        job.setOutputValueClass(IntWritable.class);
        // 5 设置最终数据输出的key和value类型
        job.setOutputKeyClass(Text.class); //没说的是Reduce说的是最终 这个案例Reduce其实就是最终的输出
        job.setOutputValueClass(IntWritable.class);
        // 6 设置输入路径和输出路径
        //使用着两个方法args[0],args[1] 方法也可以但是我比较喜欢下面直接把路径写出来
        //FileInputFormat.setInputPaths(job,new Path(args[0]));
        //FileOutputFormat.setOutputPath(job,new Path(args[1]));
        //6.1 定义统一资源标识符
        String uri = "hdfs://master:9000";
        //6.2 创建输入路径
        Path inputpath = new Path(uri+"/atguigu/hello2.txt");
        //6.3 创建输出路径,输出路径事先是不能存在的
        Path outputpath = new Path(uri + "/shangguigu");
        // 获取文件系统
        FileSystem fs =  FileSystem.get(new URI(uri), conf);
        // 删除输出目录(第二个参数设置是否递归)
        fs.delete(outputpath, true);
        // 给作业添加输入目录(允许多个)
        FileInputFormat.addInputPath(job, inputpath); //添加输入目录
        // 给作业设置输出目录(只能一个)
        FileOutputFormat.setOutputPath(job, outputpath); //添加输出目录
        // 7 提交job
        job.waitForCompletion(true); //等待提交完成,里面这个true要是提交完了后会打印一些信息,为false只是不打印信息而已仅此而已

        // 输出统计结果 将结果打印在输出台 不然只有把jar包上传到linux运行才能看到结果
        System.out.println("======统计结果======");
        FileStatus[] fileStatuses = fs.listStatus(outputpath);
        for (int i = 1; i < fileStatuses.length; i++) {
            // 输出结果文件路径
            System.out.println(fileStatuses[i].getPath());
            // 获取文件系统数据字节输入流
            FSDataInputStream in = fs.open(fileStatuses[i].getPath());
            // 将结果文件显示在控制台
            IOUtils.copyBytes(in, System.out, 4096, false);
        }
    }
}

4、运行程序,查看结果

在这里插入图片描述
或者说打包成jar包,在linux上运行也是一样的
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值