维基百科上讲:
倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。
有两种不同的反向索引形式:
1. 一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。
2. 一个单词的水平反向索引(或者完全反向索引)又包含每个单词在一个文档中的位置。
后者的形式提供了更多的兼容性(比如短语搜索),但是需要更多的时间和空间来创建。
倒排索引经常用在建立一个数据集和另外一个数据集映射的场景。目的是产生一个数据集的索引以提供对另一个数据集更快的搜索能力。
MapReduce来做倒排索引是非常简单的,因为MapReduce框架将负责处理大部分的工作。
倒排索引实例
对于关键字搜索文档算是倒排索引应用里的经典了。
下面的实例是有一批文本文件,要求找出文本文件里都有哪些词语,并统计出所有词语在哪些文件里出现过,建立词语和文件的对应关系,以便可以通过关键字快速索引文件。
package mapreduce.invertedindex;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ToolRunner;
/**
* Created by 鸣宇淳 on 2018/1/31.
* hadoop jar ~/chybin/orderdemo-1.0-SNAPSHOT.jar mapreduce.invertedindex.InverteIndexMain hdfs://ClusterTest/chybin/index hdfs://ClusterTest/chybin/out/index/1 1
*/
public class InverteIndexMain {
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
int status = ToolRunner.run(configuration, new InverteIndexMR(), args);
System.exit(status);
}
}
package mapreduce.invertedindex;
import com.huaban.analysis.jieba.JiebaSegmenter;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.SortedMapWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
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;
import org.apache.hadoop.util.Tool;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Created by 鸣宇淳 on 2018/1/31.
*/
public class InverteIndexMR extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
//获取配置
Configuration configuration = this.getConf();
//创建job
Job job = Job.getInstance(configuration, InverteIndexMR.class.getSimpleName());
//指定MapReduce主类
job.setJarByClass(InverteIndexMR.class);
//指定输入路径
FileInputFormat.addInputPath(job, new Path(args[0]));
job.setMapperClass(InverteIndexMR.InverteIndexMapper.class);
//指定输出路径
Path outpath = new Path(args[1]);
FileOutputFormat.setOutputPath(job, outpath);
//定义Map输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(SortedMapWritable.class);
//定义reducer类
job.setReducerClass(InverteIndexMR.InverteIndexReducer.class);
//定义输出类型
job.setOutputKeyClass(Text.class);
//输出值的类型
job.setOutputValueClass(SortedMapWritable.class);
//指定reduce个数
job.setNumReduceTasks(Integer.valueOf(args[2]));
//设置Combiner
job.setCombinerClass(InverteIndexReducer.class);
//添加第三方库
job.addFileToClassPath(new Path("hdfs://ClusterTest/chybin/jar/jieba-analysis-1.0.2.jar"));
boolean isSucces = job.waitForCompletion(true);
return isSucces ? 0 : 1;
}
public static class InverteIndexMapper extends Mapper<Object, Text, Text, SortedMapWritable> {
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
//所在文件的文件名
FileSplit fileSplit = (FileSplit) context.getInputSplit();
final String fileName = fileSplit.getPath().getName();
//结巴分词
JiebaSegmenter segmenter = new JiebaSegmenter();
List<String> words = segmenter.sentenceProcess(value.toString());
//每一个词语都做为map输出
for (final String word : words) {
SortedMapWritable map = new SortedMapWritable();
map.put(new Text(fileName), new IntWritable(1));
//map输出:key为词语,value为Map对象,Map对象包括所在文件和单词出现的个数
context.write(new Text(word), map);
}
}
}
public static class InverteIndexReducer extends Reducer<Text, SortedMapWritable, Text, SortedMapWritable> {
@Override
protected void reduce(Text key, Iterable<SortedMapWritable> values, Context context) throws IOException, InterruptedException {
//将相同词语的合并,合并内容有:出现在哪些文件里、出现的个数
//同一个词语,合并为一个outMap对象
SortedMapWritable outMap = new SortedMapWritable();
for (SortedMapWritable map : values) {
for (Map.Entry<WritableComparable, Writable> entry : map.entrySet()) {
if (outMap.get(entry.getKey()) == null) {
//如果当前词语所对应的outMap对象里还没有这个文件名,就添加进对象
outMap.put(entry.getKey(), entry.getValue());
} else {
//如果当前词语所对应的outMap对象里已经有了这个文件,就把出现个数相加
int count = ((IntWritable) outMap.get(entry.getKey())).get() + ((IntWritable) entry.getValue()).get();
outMap.put(entry.getKey(), new IntWritable(count));
}
}
}
context.write(key, outMap);
}
}
}