MapReduce编程——倒排索引

需求:统计每个单词在每个文件中出现的词频

有2个文件:a.txt b.txt
a.txt 文件中的内容:
<0,”hello tom”>
<1,”hello kitty”>
<2,”hello world”>
<3,”hello jerry”>

b.txt 文件中的内容:
<0,”hello tom”>
<1,”hello tom”>
<2,”hello jerry “>
想要得到的最终结果形式:
hello “a.txt->5 b.txt->3”
tom “a.txt->2 b.txt->1”
kitty “a.txt->1”

原理剖析:总共分3个阶段
——————— 第一阶段:Map阶段————————————-
输入:
a.txt 文件中的内容:
<0,”hello tom”>
<1,”hello kitty”>
<2,”hello world”>
<3,”hello jerry”>

b.txt 文件中的内容:
<0,”hello tom”>
<1,”hello tom”>
<2,”hello jerry “>

输出:
context.write(“hello->a.txt”,”1”);
context.write(“tom->a.txt”,”1”);
context.write(“hello->a.txt”,”1”);
context.write(“kitty->a.txt”,”1”);
context.write(“hello->a.txt”,”1”);
context.write(“world->a.txt”,”1”);
context.write(“hello->a.txt”,”1”);
context.write(“jerry->a.txt”,”1”);

context.write(“hello->b.txt”,”1”);
context.write(“tom->b.txt”,”1”);
context.write(“hello->b.txt”,”1”);
context.write(“tom->a.txt”,”1”);
context.write(“hello->a.txt”,”1”);
context.write(“jerry->a.txt”,”1”);

——————— Combiner阶段———————————–
输入:
<”hello->a.txt”,”1”>
<”hello->a.txt”,”1”>
<”hello->a.txt”,”1”>
<”hello->a.txt”,”1”>
<”hello->a.txt”,”1”>

<”hello->b.txt”,”1”>
<”hello->b.txt”,”1”>
<”hello->b.txt”,”1”>

输出:
context.write(“hello”,”a.txt->4”);
context.write(“hello”,”b.txt->3”);

————————— Reducer阶段—————————–
输入:

<”hello”,{“a.txt->5”,”b.txt->3”}>

输出:
context.write(“hello”,”a.txt->5 b.txt->3”);


最终结果:
hello  “a.txt->5 b.txt->3”
tom  “a.txt->2 b.txt->1”
kitty  “a.txt->1”

实现代码如下:

import java.io.IOException;

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

public class InverseIndex {

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();

        Job job = Job.getInstance(conf);
        //设置jar
        job.setJarByClass(InverseIndex.class);

        //设置Mapper相关的属性
        job.setMapperClass(IndexMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);
        FileInputFormat.setInputPaths(job, new Path(args[0]));//words.txt

        //设置Reducer相关属性
        job.setReducerClass(IndexReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        job.setCombinerClass(IndexCombiner.class);

        //提交任务
        job.waitForCompletion(true);
    }
    /**
      * Map阶段
     **/
    public static class IndexMapper extends Mapper<LongWritable, Text, Text, Text>{

        private Text k = new Text();
        private Text v = new Text();
        @Override//重写map方法
        protected void map(LongWritable key, Text value,
                Mapper<LongWritable, Text, Text, Text>.Context context)
                throws IOException, InterruptedException {
            //将文件中的内容一行一行读出 输入内容的原型应该是<0,"hello tom"> 行号作为key,一行内容作为value
            String line = value.toString();
            //使用空格分割字符串
            String[] fields = line.split(" ");
            //得到该map的输入切片
            FileSplit inputSplit = (FileSplit) context.getInputSplit();
            //通过输入切片获取文件路径名称
            Path path = inputSplit.getPath();
            String name = path.getName();
            //拼接字符串  文件名->出现的次数
            for(String f : fields){
                k.set(f + "->" + name);
                v.set("1");
                context.write(k, v);
            }
        }

    }
    /**
      * combiner是一个特殊的reducer,先在combiner中进行一次合并
     **/
    public static class IndexCombiner extends Reducer<Text, Text, Text, Text>{

        private Text k = new Text();
        private Text v = new Text();
        @Override//重写reduce方法(不是conbiner)
        protected void reduce(Text key, Iterable<Text> values,
                Reducer<Text, Text, Text, Text>.Context context)
                throws IOException, InterruptedException {
            //将 文件名称 和 单词出现次数 通过"->" 切分
            String[] fields = key.toString().split("->");
            long sum = 0;
            //迭代器values中的内容应该是:【1,1,1...】
            for(Text t : values){
                sum += Long.parseLong(t.toString());
            }
            //输出Key应该是 单词
            k.set(fields[0]);
            //取出这个单词对应的文件名称,和出现次数进行拼接 作为输出Value的值
            v.set(fields[1] + "->" + sum);
            context.write(k, v);
        }

    }
    /**
      * Reduce阶段 在reduce阶段进行第二次合并
      */
    public static class IndexReducer extends Reducer<Text, Text, Text, Text>{

        private Text v = new Text();
        @Override//输入 K:单词   V:文件名—>出现次数
        protected void reduce(Text key, Iterable<Text> values,
                Reducer<Text, Text, Text, Text>.Context context)
                throws IOException, InterruptedException {
            String value = "";
            //迭代器values中的内容应该是:【a.txt->5 ,b.txt->3】
            将这个值通过迭代器循环,使用"->"连接起来
            for(Text t : values){
                value += t.toString() + " ";
            }
            //key仍然是单词
            v.set(value);
            context.write(key, v);
        }

    }

}

运行:

编写好Map-Reduce程序之后,在hadoop的hdfs上传两个文件,存放至/ii目录下,即 ii/[ a.txt, b.txt ]
使用hadoop jar 命令启动运行MR例程:

hadoop jar  /root/mrs.jar(指定jar文件保存的目录)cn.itcast.hadoop.mr.ii.InverseIndex(指定jar文件中main方法的路径) /ii(输入文件a.txt b.txt在hdfs中所在的目录) /iiout(输出文件在hdfs中所在的目录)

posted @ 2016-03-29 22:15  时光.漫步  阅读(274)  评论(0编辑  收藏  举报