map函数和reduce函数的运行次数由谁来决定:
map函数:
由map输入端的记录数决定,记录有多少条(kv对有多少个),函数就调用多少次
reduce函数:
由reduce输入端的分组数目决定。默认按key进行分组(可以自定义分组)
倒排索引:(帮助理解:与正排索引的区别)
1、正向索引的不足
在搜索引擎中每个文件都对应一个文件ID,文件内容被表示为一系列关键词的集合(实际上在搜索引擎索引库中,关键词也已经转换为关键词ID)。例如“文档1”经过分词,提取了20个关键词,每个关键词都会记录它在文档中的出现次数和出现位置。
得到正向索引的结构如下:
“文档1”的ID > 单词1:出现次数,出现位置列表;单词2:出现次数,出现位置列表;…………。
“文档2”的ID > 此文档出现的关键词列表。
一般情况下,是通过 key 去找 value。
打个比方,当用户在主页上搜索关键词“华为手机”时,假设只存在正向索引(forward index),那么就需要扫描索引库中的所有文档,找出所有包含关键词“华为手机”的文档,再根据打分模型进行打分,排出名次后呈现给用户。因为互联网上收录在搜索引擎中的文档的数目是个天文数字,这样的索引结构根本无法满足实时返回排名结果的要求。
所以,搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。
得到倒排索引的结构如下:
“关键词1”:“文档1”的ID,“文档2”的ID,…………。
“关键词2”:带有此关键词的文档ID列表。
单词-文档矩阵是表达两者之间所具有的一种包含关系的概念模型,下图展示了其含义。图中每列代表一个文档,每行代表一个单词,打对勾的位置代表包含关系。
2.倒排索引基本概念
文档(Document):一般搜索引擎的处理对象是互联网网页,而文档这个概念要更宽泛些,代表以文本形式存在的存储对象,相比网页来说,涵盖更多种形式,比如Word,PDF,html,XML等不同格式的文件都可以称之为文档。再比如一封邮件,一条短信,一条微博也可以称之为文档。
文档集合(Document Collection):由若干文档构成的集合称之为文档集合。比如海量的互联网网页或者说大量的电子邮件都是文档集合的具体例子。
文档编号(Document ID):在搜索引擎内部,会将文档集合内每个文档赋予一个唯一的内部编号,以此编号来作为这个文档的唯一标识,这样方便内部处理,每个文档的内部编号即称之为“文档编号”,后文有时会用DocID来便捷地代表文档编号。
单词编号(Word ID):与文档编号类似,搜索引擎内部以唯一的编号来表征某个单词,单词编号可以作为某个单词的唯一表征。
倒排索引(Inverted Index):倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
单词词典(Lexicon):搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
倒排列表(PostingList):倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。
倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
关于这些概念之间的关系,通过下图可以比较清晰的看出来。
下面通过一个小案例来实现“倒排索引”:
word1.txt
MapReduce is simple
word2.txt
MapReduce is powerful is simple
word3.txt
Hello MapReduce bye MapReduce
统计结果为:
MapReduce word1.txt 1 word2.txt 1 word3.txt 2
is word1.txt 1 word2.txt 2
simple word1.txt 1 word2.txt 1
............
<k1,v1>
<k2,v2> map输出,单词和文件名作为key, 1作为value
MapReduce+word1.txt 1
MapReduce+word3.txt 1
MapReduce+word3.txt 1
<k2,v2> combiner的输入就是就是map的输出,因此,同一个键MapReduce+word3.txt对应的value为[1,1]
combiner的输出为 单词作为key,文件名和次数作为value.
MapReduce,word1.txt 1
MapReduce,word3.txt 2
<k3,v3> reduce端的输入就是combiner的输出,key:[MapReduce] value:【word1.txt 1,word3.txt 2】
reduce端的输出:key,value的迭代拼接
MapReduce, word1.txt 1 word3.txt 2
public class IndexMapper extends Mapper<LongWritable, Text, Text, Text> {
Text k2 = new Text();
Text v2 = new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
FileSplit split = (FileSplit) context.getInputSplit();
String filename = split.getPath().getName();
System.out.println(filename);
StringTokenizer st = new StringTokenizer(value.toString(), " ");
while(st.hasMoreTokens()) {
String str = st.nextToken();
System.out.println("k1:"+str);
k2.set(str+"|"+filename);
v2.set("1");
System.out.println("k2:"+k2.toString());
context.write(k2, v2);
}
}
}
public class IndexCombiner extends Reducer<Text, Text, Text, Text>{
Text t = new Text();
Text v = new Text();
@Override
protected void reduce(Text key, Iterable<Text> vs, Context context)
throws IOException, InterruptedException {
System.out.println("------------------------");
String[] arr = key.toString().split("\\|");
int count = 0;
for(Text l:vs) {
count+=Integer.parseInt(l.toString());
}
t.set(arr[0]);
v.set(arr[1]+":"+count);
context.write(t, v);
}
}
public class IndexReducer extends Reducer<Text, Text, Text, Text>{
@Override
protected void reduce(Text key, Iterable<Text> vs, Reducer<Text, Text, Text, Text>.Context context)
throws IOException, InterruptedException {
System.out.println("++++++++++++++=");
String str = "";
for(Text v:vs) {
str+=v.toString()+"\t";
}
context.write(key, new Text(str));
}
}
public class IndexDriver {
public static void main(String[] args) throws Exception {
Job job = Job.getInstance();
job.setJobName(" index");
job.setJarByClass(IndexDriver.class);
job.setMapperClass(IndexMapper.class);
job.setReducerClass(IndexReducer.class);
job.setCombinerClass(IndexCombiner.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(job, new Path("file:///D:/data/倒排索引/*"));
FileOutputFormat.setOutputPath(job, new Path("file:///D:/index9"));
System.exit(job.waitForCompletion(true)?0:1);
}
}