文中所用的图例来自这篇文章,不正之处,欢迎指教。
倒排索引是文档检索中最为常用的数据结构,根据单词来查看在文档中出现的频率。通常情况下,倒排索引由一个单词以及与其相关的文档列表组成。在Hadoop学习过程中,倒排索引是经常会出现的一个MapReduce实例,本文将会给出一个倒排索引mapreduce实现的实例。
1.倒排索引
上图是一个倒排索引的实例,可以看出,对于一个单词,其对应着若干个出现该单词的文档以及一个权值,实际中这个权值往往是单词出现的频率。例如对于下面这样三个文件中的内容可以得到如右图所示的索引文件。
右图的含义就表示单词MapReduce在文本T0中出现了一次,在T1中出现了1次,在T2中出现了2次,其余的单词依次类推。我么现在的任务就输入这些若干的文件,利用MapReduce来输出最中的索引结果。可以看出这个例子和wordcount实例很像,只不过wordcount实例中的输出并没有表明单词出现在那个文件中,只是统计了单词出现的次数而已,可以利用wordcount的思想来解决这个问题。
2.Map阶段
对于Map阶段,Map的输入还是文本,毫无疑问key值还是偏移量,value值还是每一行的内容,对于每一个单词,通过Map阶段可以读取其在某一个文本中出现的次数,如下图所示。
可以看出,Map阶段和wordcount阶段唯一的区别就是在输出value值得时候一起输出了文件名称,之所以要记住文件名称,是因为最后要统计某一个单词在某一个文档中出现的次数,所以说文件的名称是必不可少的。我们将单词和文件名称一起作为key值,将1作为value值,实现的代码如下所示:
public static class InveredIndexMapper extends
Mapper<LongWritable, Text, Text, Text> {
private Text k = new Text();
private Text v = new Text();
private FileSplit fs = new FileSplit();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// TODO Auto-generated method stub
// super.map(key, value, context);
String line = value.toString();
String arr[] = line.split(" ");
fs = (FileSplit) context.getInputSplit();
String name = fs.getPath().getName().toString(); // 得到文件的名称
for (String s : arr) {
k.set(s + ":" + name);
v.set("1");
context.write(k, v);
}
}
}
2.Combiner过程
在Map输出之后,我们要经过一个combine操作,需要将key值相同的value值进行相加,这样就可以计算出每一个单词出现在某一个文件中的次数。在combiner的输出阶段将单词作为key值,文件名称和出现的次数作为value值,如下图所示:
combine过程的实现代码如下所示:
public static class InveredIndexCombiner extends
Reducer<Text, Text, Text, Text> {
private Text k = new Text();
private Text v = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
// TODO Auto-generated method stub
// super.reduce(arg0, arg1, arg2);
String key1 = key.toString();
long sum = 0;
for (Text t : values) {
sum += Long.parseLong(t.toString());
}
int index = key1.indexOf(":");
k.set(key1.substring(0, index));
v.set(key1.substring(index + 1) + ":" + sum);
context.write(k, v);
}
}
经过Map和combiner的操作过程,现在的数据已经可以说明每一个单词在某一个文档中出现的次数了,现在需要做的只是将这些value值进行一个合并即可。
实现的过程如下:
public static class InveredIndexReducer extends Reducer<Text, Text, Text, Text>{
private Text k=new Text();
private Text v=new Text();
@Override
protected void reduce(Text key, Iterable<Text> values,Context context)
throws IOException, InterruptedException {
// TODO Auto-generated method stub
//super.reduce(arg0, arg1, arg2);
String s=new String();
for (Text t : values) {
s+=t.toString()+";";
}
v.set(s);
k.set(key);
context.write(k, v);
}
}
接下来是主程序部分:
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// TODO Auto-generated method stub
Configuration cf=new Configuration();
Job job=Job.getInstance();
job.setJarByClass(InvertedIndex.class);
job.setMapperClass(InveredIndexMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
job.setReducerClass(InveredIndexReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.setCombinerClass(InveredIndexCombiner.class);
job.waitForCompletion(true);
}
提交程序(输入文件为自己定义的.txt文件,只是用于实验,不存在实际意义),最终的结果如下所示: