倒排索引介绍:
倒排索引是被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射,是目前几乎所有支持全文索引的搜索引擎都需要依赖的一个数据结构。
倒排索引的设计思路:
Map过程:
Map过程首先必须分析输入的key,value对,得到倒排索引中需要的三个信息:单词、文档名和词频,将单词和URL组成key值(如”MapReduce:test1.txt”),将词频作为value,这样做可以利用MapReduce框架自带的Map排序,将同一文档的相同单词的词频组成列表,传递给Combine过程
Combine过程:
经过map方法处理后,Combine过程将key值相同的value值累加,得到一个单词在文档在文档中的词频。在Combine过程中将单词作为key值,文档名和词频组成value值(如”test1.txt:1”)。将相同单词的所有记录发送给同一个Reducer进行处理。
Reduce过程:
经过上述两个过程后,Reduce过程只需将相同key值的value值组合成倒排索引文件所需的格式即可,其中还需要写一个相加的方法来求取一个单词在所有文档中的频次total。
package org.apache.hadoop.examples;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
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 class Map extends Mapper<Object,Text,Text,Text>
{
private URI[] remoteFiles; // 存放停用词txt文档统一资源标识符
private Set<String> stopwords; //存放停用词
//重写setup函数
@Override
public void setup(Context context) throws IOException
{
Configuration conf = context.getConfiguration();
remoteFiles = Job.getInstance(conf).getCacheFiles(); //获取共享资源里的stop_words.txt
stopwords = new TreeSet<String>();
//对于URI列表里的每一个停用词表(假设有多个 停用词表.txt)
for (int i = 0; i < remoteFiles.length; i++)
{
FileInputStream in =new FileInputStream(new Path(remoteFiles[i].getPath()).getName().toString()); //获取文件
//读取文件的每一行
Scanner sc =new Scanner(in);
while (sc.hasNextLine())
{
String line = sc.nextLine();
String[] split = line.trim().split(" ");
for (int j = 0; j < split.length; j++)
{
stopwords.add(split[j]); //加到set集合中
}
}
}
}
public void map(Object key, Text value, Context context) throws IOException, InterruptedException
{
FileSplit inputSplit = (FileSplit) context.getInputSplit();
String s=inputSplit.getPath().getName(); //获取文件名
//System.out.println(s);
String t[]=value.toString().split("\\W"); //按照非数字、单词进行划分
int num=0;
while(num<t.length-1)
{
String tt=t[num].toLowerCase();
num++;
//如果该词不是停用词
if(!stopwords.contains(tt))
context.write(new Text(tt+","+s), new Text("1")); // context中写入((token,xxx.txt),1)
}
}
}
public static class Combine extends Reducer<Text,Text,Text,Text>
{
public void reduce(Text key, Iterable<Text> values, Context context)throws IOException, InterruptedException
{
//Map结束后传进来的key为 word,test.txt value为1
Text temp=new Text();
int sum=0;
for(Text val:values)
{
sum+=1; //将相同key的value相加
}
//然后将key为 abc,test1.txt; sum -->转变为 abc; test1.txt,sum 以便于reduce
String word[]=key.toString().split(",");
temp.set(word[1]+","+sum);
context.write(new Text(word[0]), temp);
}
}
public static class Reduce extends Reducer<Text,Text,Text,Text>
{
public void reduce(Text key, Iterable<Text> values, Context context)throws IOException, InterruptedException
{
Text temp=new Text();
int num=0; //该单词在所有文档中出现的总的次数
String fileindex=new String();
for(Text val:values)
{
fileindex+="<"+val.toString()+">"+";"; //所有的<文档,频次>对
String word[]=val.toString().split(",");
num+=Integer.parseInt(word[1]); //将每个文档的次数相加
}
fileindex+="<total,"+String.valueOf(num)+">."; //在value串的最后加上total的值
temp.set(fileindex);
context.write(key, temp); //输出结果
}
}
//main方法
public static void main(String[] args) throws Exception {
//在学校集群上跑的
final String OUTPUT_PATH = "/user/201500xx/output";
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://xx.xxx.x.xxx:9000");
Path path = new Path(OUTPUT_PATH);
//加载配置文件
FileSystem fileSystem = path.getFileSystem(conf);
//输出目录若存在则删除
if (fileSystem.exists(new Path(OUTPUT_PATH)))
{
fileSystem.delete(new Path(OUTPUT_PATH),true);
}
//指定输入输出目录
String[] otherArgs = new String[]{"/txt_input","/user/201500xx/output"};
if (otherArgs.length != 2)
{
System.err.println("路径出错");
System.exit(2);
}
//一些初始化
Job job = Job.getInstance(conf);
job.addCacheFile(new Path("hdfs://xx.xxx.x.xxx:9000/stop_words/stop_words_eng.txt").toUri());
job.setJarByClass(InverseIndex.class);
job.setMapperClass(Map.class); //初始化为自定义Map类
job.setCombinerClass(Combine.class);
job.setReducerClass(Reduce.class); //初始化为自定义Reduce类
job.setOutputKeyClass(Text.class); //指定输出的key的类型,Text相当于String类
job.setOutputValueClass(Text.class); //指定输出的Value的类型,Text相当于String类
FileInputFormat.addInputPath(job, new Path(otherArgs[0])); //FileInputFormat指将输入的文件(若大于64M)进行切片划分,每个split切片对应一个Mapper任务
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
代码有很清晰的注释,看不懂的话可以评论给我,input目录文件及运行结果output目录如下: