MapReduce之倒排索引
一、相关说明
-
倒排列表概念 :
倒排列表用来记录有哪些文档包含了某个单词。一般在文档集合里会有很多文档包含某个单词,每个文档会记录文档编号(DocID),单词在这个文档中出现的次数(TF)及单词在文档中哪些位置出现过等信息,这样与一个文档相关的信息被称做倒排索引项(Posting),包含这个单词的一系列倒排索引项形成了列表结构,这就是某个单词对应的倒排列表。图1是倒排列表的示意图,在文档集合中出现过的所有单词及其对应的倒排列表组成了倒排索引。
在实际的搜索引擎系统中,并不存储倒排索引项中的实际文档编号,而是代之以文档编号差值(D-Gap)。文档编号差值是倒排列表中相邻的两个倒排索引项文档编号的差值,一般在索引构建过程中,可以保证倒排列表中后面出现的文档编号大于之前出现的文档编号,所以文档编号差值总是大于0的整数。如图2所示的例子中,原始的 3个文档编号分别是187、196和199,通过编号差值计算,在实际存储的时候就转化成了:187、9、3。
之所以要对文档编号进行差值计算,主要原因是为了更好地对数据进行压缩,原始文档编号一般都是大数值,通过差值计算,就有效地将大数值转换为了小数值,而这有助于增加数据的压缩率。
-
倒排索引概念 :
倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
倒排索引有两种不同的反向索引形式:
一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。
一个单词的水平反向索引(或者完全反向索引)又包含每个单词在一个文档中的位置。
后者的形式提供了更多的兼容性(比如短语搜索),但是需要更多的时间和空间来创建。
现代搜索引擎的索引都是基于倒排索引。相比“签名文件”、“后缀树”等索引结构,“倒排索引”是实现单词到文档映射关系的最佳实现方式和最有效的索引结构。
-
要求:求出每个单词在各个文档中出现的次数及所在文档,结果如下。
二、测试数据
-
测试数据如下
1)文件一:data01.txt,内容如下:I love Beijing
2)文件二:data02.txt,内容如下:
I love China
3)文件三:data03.txt,内容如下:
Beijing is the capital of China
- 将三个文件放置在相同的目录下
三、编程思路
- 思路:
1、按照wordcount的思路,添加一个combiner对数据先做一次处理
2、保持kev/value对数据类型一致
四、实现步骤
-
在Idea或eclipse中创建maven项目
-
在pom.xml中添加hadoop依赖
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-common</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-core</artifactId> <version>2.7.3</version> </dependency>
-
添加log4j.properties文件在资源目录下即resources,文件内容如下:
### 配置根 ### log4j.rootLogger = debug,console,fileAppender ## 配置输出到控制台 ### log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %d{ABSOLUTE} %5p %c:%L - %m%n ### 配置输出到文件 ### log4j.appender.fileAppender = org.apache.log4j.FileAppender log4j.appender.fileAppender.File = logs/logs.log log4j.appender.fileAppender.Append = false log4j.appender.fileAppender.Threshold = DEBUG,INFO,WARN,ERROR log4j.appender.fileAppender.layout = org.apache.log4j.PatternLayout log4j.appender.fileAppender.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
-
编写文本类型的mapper即InvertedIndexMapper
import java.io.IOException; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.lib.input.FileSplit; public class RevertedIndexMapper extends Mapper<LongWritable, Text, Text, Text> { @Override protected void map(LongWritable key1, Text value1, Context context) throws IOException, InterruptedException { // 数据:I love Beijing and love Shanghai //得到数据来自哪个文件??? /mydata/data01.txt String path = ((FileSplit)context.getInputSplit()).getPath().toString(); //得到文件名 //最后一个斜线 int index = path.lastIndexOf("/"); //文件名 String fileName = path.substring(index + 1); //分词 String[] words = value1.toString().split(" "); for(String w:words){ //格式: 单词:文件名 context.write(new Text(w+":"+fileName), new Text("1")); } } }
-
编写Combiner,先做一次合并操作
import java.io.IOException; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class RevertedIndexCombiner extends Reducer<Text, Text, Text, Text> { @Override protected void reduce(Text k21, Iterable<Text> v21, Context context) throws IOException, InterruptedException { // 求和:对同一个文件中,每个单词的频率求和 int total = 0; for(Text v:v21){ total = total + Integer.parseInt(v.toString()); } //输出 //分离:单词和文件名 k21: love:data01.txt String data = k21.toString(); //找到冒号的位置 int index = data.indexOf(":"); String word = data.substring(0, index); //单词 String fileName = data.substring(index+1);//文件名 //输出 context.write(new Text(word), new Text(fileName+":"+total)); } }
-
编写reducer类
import java.io.IOException; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class RevertedIndexReducer extends Reducer<Text, Text, Text, Text> { @Override protected void reduce(Text k3, Iterable<Text> v3, Context context) throws IOException, InterruptedException { //对combiner的输出的value 拼加 String str = ""; for(Text t:v3){ str = "(" + t.toString()+")" + str; } context.write(k3, new Text(str)); } }
-
编写Driver类
import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DoubleWritable; 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; public class RevertedIndexMain { public static void main(String[] args) throws Exception { // 创建一个job和任务入口 Job job = Job.getInstance(new Configuration()); job.setJarByClass(RevertedIndexMain.class); //main方法所在的class //指定job的mapper和输出的类型<k2 v2> job.setMapperClass(RevertedIndexMapper.class); job.setMapOutputKeyClass(Text.class); //k2的类型 job.setMapOutputValueClass(Text.class); //v2的类型 //引入combiner job.setCombinerClass(RevertedIndexCombiner.class); //指定job的reducer和输出的类型<k4 v4> job.setReducerClass(RevertedIndexReducer.class); job.setOutputKeyClass(Text.class); //k4的类型 job.setOutputValueClass(Text.class); //v4的类型 //指定job的输入和输出 FileInputFormat.setInputPaths(job, new Path("F:\\NIIT\\hadoopOnWindow\\input\\invertedindex")); FileOutputFormat.setOutputPath(job, new Path("F:\\NIIT\\hadoopOnWindow\\input\\invertedindex\\out")); //执行job job.waitForCompletion(true); } }
-
本地运行代码,测试下结果正确与否,结果如下:
五、打包上传到集群中运行(仅供参考,自行修改)
-
本地运行测试结果正确后,需要对Driver类输出部分代码进行修改,具体修改如下:
FileOutputFormat.setOutputPath(job,new Path(args[0])); -
修改Job中【数据库】相关的信息
-
将程序打成jar包,需要在pom.xml中配置打包插件
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId> maven-assembly-plugin </artifactId> <configuration> <!-- 使用Maven预配置的描述符--> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <!-- 绑定到package生命周期 --> <phase>package</phase> <goals> <!-- 只运行一次 --> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
按照如下图所示进行操作
-
提交集群运行,执行如下命令:
hadoop jar packagedemo-1.0-SNAPSHOT.jar com.niit.mr.EmpJob /datas/emp.csv /output/emp/
至此,所有的步骤已经完成,大家可以试试,祝大家好运~~~~