什么叫做全文索引呢?这要从我们生活中的数据说起。
我们生活中的数据总体分为两种:结构化数据和非结构化数据:结构化数据—指具有固定格式或有限长度的数据,如数据库,元数据等;非结构化数据—指不固定长或无固定格式的数据,如邮箱,Word文档等;半结构化数据—如XML、HTML等,当根据需要可按照结构化数据来处理,也可抽取出纯文本按非机构化数据来处理;非结构化数据有一种叫法叫全文数据。
按照数据的分类,搜索也分为两种:对结构化数据的搜索—如对数据库的搜索,用sql语句。再如对元数据的搜索,如利用windows搜索对文件名、类型、修改时间进行搜索等;对非结构化数据的搜索----如利用windows的搜索也可以搜索文件内容,linux下的grep命令,再如用Google和百度可以搜索大量内容数据。
对非结构化数据,即对全文数据的搜索主要有两种方法:
一种是“顺序扫描法”:所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含字此字符串,则此文档为我们要找的文件,接着看下一个文件,知道扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是想当的慢。
对非结构化数据扫描很慢,对结构化数据的搜索却相对较快,那么把非结构化数据想办法能的有一定结构不就行了吗?
这种想法很天然,却构成了全文搜索的基本思路,即将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。
这部分从非结构化数据中提取出来的然后重新组织的信息,我们知之为索引。
这种说法比较抽象,举几个例子就很容易明白,比如字典,字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。
1.1. 全文索引定义
这种先建立索引,再对索引进行搜索的过程就叫全文索引。
下图来自于《Lucene in action》,但却不仅仅描述了Lucene的检索过程,而是描述了全文检索的一般过程。
全文索引大体分为两个过程,索引创建(indexing)和索引检索(search)。创建索引—将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程;索引检索—就是得到用户的检索请求,搜索创建的索引,然后返回结果的过程。
于是全文检索就存在三个重要问题:1、索引里面究竟存些什么(index)2、如何创建索引(indexing)3、如果对索引进行搜索(search)。
1.2. 索引里面究竟存些什么
首先来看为什么顺序扫描的速度慢:其实由于我们想要搜索的信息和非结构化数据中存储的信息不一致造成的。
非结构化数据中存储的信息是每个文件包含哪些字符串,也即已知文件,欲求字符串相对容易,也即从文件到字符串的映射。而我们想搜索的信息是哪些文件包含此字符串,也即已知字符串到文件的映射。两者恰恰相反。于是如果索引总能够保存从字符串到文件的映射,则会大大提高搜索速度。
由于从字符串到文件的映射是文件到字符串映射的反向过程,于是保存这种信息的索引称为反向索引。
反向索引的保存的信息一般如下:假设文档集合里有100篇文档,为了方便表示,我们为文档编号从1到100,得到下面的结构。
左边保存的是一系列字符串,成为字典。
每个字符串只想包含此字符串的文档链表,此文档链表称为倒排表(posting list)。有了索引,便使保存的信息和要搜索的信息一致,可以大大加快搜索的速度。
比如说,我们要寻找既包含字符串“Lucene”又包含字符串“solr”的文档,只需要以下几个步骤:
- 取出包含字符串“Lucene”的文档链表;
- 取出包含字符串“solr”的文档链表;
- 通过合并链表,找出既包含“Lucene”又包含“solr”的文件。
看到这个地方,有人可能会说,全文索引的确加快了搜索的速度,但是多了索引的过程,两者加起来不一定比顺序扫描快多少。的确,加上索引的过程,全文检索不一定比顺序扫描快,尤其是在数据量小的时候更是如此。而对一个很大量的数据创建索引也是一个很慢的过程。
然后两者还是有区别的,顺序扫描是每次都要扫描,而创建索引的过程仅仅需要一次,以后便一劳永逸了,每次搜索,创建索引的过程不必经过,仅仅搜索已经创建好的索引就可以了。这也是全文搜索相对于顺序扫描的优势之一:一次索引,多次使用。
1.3. 如何创建索引
全文索引的索引创建过程一般有一下几步:
1.3.1. 一些要索引的原文档(document)
为了方便说明索引创建过程,这里特意用两个文件为例:
文件一:student should be allowed to go out with their friends ,but not allowed to drink beer.
文件二:my friend jerry went to school to see his students but found them drunk which is not allowed.
1.3.2.将原始文档传给分词组件(Tokenizer)
分词组件(Tokenizer)会做一下几件事情(此过程成为Tokenize):
- 将文档分成一个一个单独的单词;
- 去除标点符号;
- 去除停词(stop word);
所谓停词(stop word)就是一种语言中最普通的一些单词,由于没有特别的意义,因而大多数情况下不能成为搜索的关键词,因而创建索引时,这种词会被去掉而减少索引的大小。
英语中停词(stop word)如:the、a、this等。对于每一种语言的分词组件,都有一个停词集合。
经过分词后得到的结果成为词元(Token)。在上面的例子中,得到的词元如下:student、allowed、go、their、friends、allowed、drink、beer、my、friend、Jerry等。
1.3.3.将得到的分词(Token)传给语言处理组件
语言处理组件(linguistic processor)主要是对得到的词元做一些同语言相关的处理。
对于英语,语言处理组件一般做一下几点:
- 变为小写;
- 将单词缩减为词根形式,如“cars”到”car“等。这种操作成为:steamming。
- 将单词转变为词根形式,如“drove”到“drive”等。这种操作成为:lemmatization。
Stemming和lemmatization的异同:
相同之处:他们都要是词汇成为词根形式。
两者的方式不同:stemming采用的是“缩减”的方式;lemmatization采用的是“转变”的方式。
两者的算法不同:stemming主要采用某种固定的算法来做这种缩减,如去“s”,去“ing”加“e”,将“ational”变为“ate”等;lemmatization主要采用保存某种字典的方式做这种转变。比如查询字典进行转换。
语言处理组件的结果成为词(term)。
1.3.4.将得到的词(term)传给索引词组(indexer)
索引组件(indexer)主要做以下几件事情:
1、 利用得到的词(term)创建一个字典。
2、 对字典按字母顺序进行排序;
3、 合并相同的词(term)成为文档倒排(posting list)链表,如下:
在此表中,有几个定义:
- Document frequently即文档频次,表示总共有多少个文件包含此词(term);
- Frequency即词频率,表示此文件中包含了几个此词(term)
对词(Term) “allow”来讲,总共有两篇文档包含此词(Term),从而词(Term)后面的文档链表总共有两项,第一项表示包含“allow”的第一篇文档,即1号文档,此文档中,“allow”出现了2次,第二项表示包含“allow”的第二个文档,是2号文档,此文档中,“allow”出现了1次。
到此为止,索引已经创建好了,可以通过它很快的找到我们想要的文档。而且在这个过程中,我们惊喜的发现,drive drove也能搜索到,因为我们的索引中,driving drove driven都会经过语言处理变成drive,在搜索时,如果您输入“driving”,输入的查询语句同样经过我们这里的一到三步,从而变成为查询“drive”,从而可以搜到我们想要的文档。
1.4. 如何对索引进行搜索
创建好索引后似乎可以宣布“我们找到想要的文档了”。然而事情并没有结束,找到了仅仅是全文索引的一个方面,因为我们得到的结果可能是一个成千上万的记录,那将谁排在最前面呢?如何判断搜索出的文档和查询语句的相关性呢?这里要回到我们第三个问题:如何对索引进行搜索?
1.4.1.用户输入查询语句
查询语句同我们普通的语言一样,也是有一定语法的。不同的查询语句有不同的语法,并且查询语句的语法根据全文索引系统的实现不同而不同。例如,用户输入语句:Lucene and learned not hadoop,说明用户想找一个Lucene和learned然而不包括hadoop的文档。
1.4.2.对查询语句进行语法分析及语言处理
由于查询语句有语法,因而也要进行语法分析,语法分析及语言处理。
1、语法分析主要用来识别单词和关键词。如上面的例子,经过语法分析,得到单词有Lucene、learned、hadoop,关键词有and、not。
2、语法分析主要是根据查询语句的语法规则来形成一棵语法树。如果发现查询语句不满足语法规则,则会报错。如上面的例子则会形成如下的语法树:
3、 语言处理同索引过程中的语言处理几乎相同
1.4.3. 搜索索引得到符合语法树的文档
此步骤有分为几个小步:
首先、在反向索引表中,分别查找出包含Lucene,learn,hadoop的文档链表;
其次,对包含Lucene,learn的链表进行合并操作,得到即包含Lucene又包含learn的文档链表;
然后,将此链表与hadoop的文档链表进行差集操作,去除包含hadoop的文档,从而得到既包含Lucene又包含learn而且不包含hadoop的文档链表。此文档链表就是我们要找的文档。
1.4.4. 根据得到的文档和查询语句的相关性对结果进行排序
虽然在上一步,我们得到了想要的文档,然后对于查询结果应该按照与查询语句的相关性进行排序,越相关者越靠前。
我们吧查询语句看做一片短小的文档,对文档与文档之间的相关性(relevance)进行打分(scoring),分数高的相关性好,就应该排在前面。
如何判断文档之间的关系?
首先,一个文档有很多词(term)组成;其次,对于文档之间的关系,不同的term重要性不同,因而判断文档之间的关系,需要找出那些词对文档之间的关系重要,然后判断这些词之间的关系。
找出词对文档的重要性的过程就称为计算词的权重(term weight)的过程。计算词的权重有两个参数:一个是词(term);另一个是文档(document)。
判断词(term)之间的关系从而得到文档相关性的过程应用一种叫做向量空间模型的算法。
1、计算权重的过程
影响一个词在一篇文档中的重要性主要有两个原因:term frequency(tf)—即此term在此文档中出现了多少次。Tf越大说明越重要;document frequency(df)--既有多少文档包含词term。Df越大说明越不重要。
计算公式如下:
这仅仅是term weight计算公式的简单典型实现。实现全文检索系统的人会有自己的实现,Lucene就与此稍有不同。
2、判断term之间的关系从而得到文档相关性的过程,也即向量空间模型的算法(VSM)。
我们把文档看做一系列词(term),每一个词都有一个权重,不同的词根据自己在文档中的权重来影响文档相关性的打分计算。于是我们把所有词文档中的权重看做一个向量:
Document={term1、term2、….、termN}
Document vector={weight1,weight2,…,weightN}
同样我们把查询语句看做一个简单的文档,也用向量来表示:
Query={term1、term2、…、term N}
Query vector={weight1、weight2、…、weight N}
我们吧所有搜索出的文档向量及查询向量放到一个N维空间中,每个词是一维。如图:
我们认为两个向量之间的夹角越小,相关性越大。所以我们计算夹角的余弦值作为相关性的打分,夹角越小,余弦值越大,打分越高,相关性越大。
有人会问,查询语句一般都很短的,包含的词是很少的,因而查询向量的维数很小,而文档很长,包含词很多,文档向量维数很大,你的途中两者维数怎么都是N呢?
在这里,既然要放到相同的向量空间,自然维数是相同的,不同时,取两者的并集,如果不含某个词时,则权重为0。相关性打分计算公式如下:
举个例子,查询语句有11个词,共有三篇文档搜索出来,其中各自的权重,如下表:
于是计算,三篇文档同查询语句的相关性打分分别为:
于是文档二相关性最高,先返回,其次是文档一,最后是文档三。
锁了这么多,其实还没有进入到Lucene,而仅仅是信息检索技术中的基础理论,然后当我们看过Lucene后我们会发现,Lucene是对这种基本理论的一种基本的实践。所以在以后的分析Lucene的文章中,会常常看到以上理论在Lucene中的应用。
1.4.5.总结
在进入Lucene之前,对上述索引创建和搜索过程做一个总结,如下图:
索引过程:
1) 有一系列被索引文件
2) 索引文件经过语法分析和语言处理形成一系列词
3) 经过索引创建形成词典和反向索引表
4) 通过索引存储将索引写入磁盘
搜索过程:
1)用户输入查询语句
2)对查询语句经过语法分析和语言分析得到一系列词
3)通过语法分析得到一个查询树
4)通过索引存储将索引读入到内存
5)利用查询树搜索索引,从而得到么个词的文档链表,对文档链表进行交叉,并得到结果文档
6)将搜索到的结果文档对查询的相关性进行排序
7)返回查询结果给用户。
2. Lucene的总体架构
Lucene总的来说是:一个高效的,可扩展的,全文检索库;全部用java实现,无需配置;及支持纯文本文件的索引和搜索;不负责有其他格式的文件抽取纯文本文件,或从网络中抓取文件的过程。
在Lucene in action中,Lucene的架构和过程如下图:
Lucene的各组件为:
- 被索引的文档用document对象表示;
- IndexWrite通过函数adddocument将文档添加到索引中,实现创建索引的过程;
- Lucene的索引是应用反向索引;
- 当用户有请求时,query代表用户的查询语句;
- Indexsearcher通过函数search搜索Lucene index。
- IndexSearcher计算term weight和score并且将结果返回给用户;
- 返回给用户的文档集合用topDocsCollector表示;
2.1. Lucene API的调用实现索引和搜索过程
索引过程如下:
- 创建一个indexWrite用来索引文件,他有几个参数,index_dir就是索引文件所存放的位置,Analyzer便是用来对文档进行分词语法分析和语言处理的;
- 创建一个document代表我们要索引的文档;
- 将不同的field加入到文档中。我们知道一篇文章有多种信息,如题目,作者,修改时间,内容等。不同类型的信息用不同的field来表示,在本例子中,一共有两类信息进行了索引,一个是文件路径,一个是文件内容。其中FileReader的SRC_FILE就是表示要索引的源文件。
- IndexWriter调用函数addDocument将索引写到索引文件夹中;
搜索过程如下:
- IndexReader将磁盘上的索引信息读入到内存,INDEX_DIR就是索引文件存放的位置。
- 创建IndexSearcher准备进行搜索;
- 创建analyer用来对查询语句进行语法分析和语言处理;
- 创建queryparser用来对查询语句进行语法分析;
- QueryParser调用parser进行语法分析,形成查询语法树,放到Query中。
- IndexSearcher调用search对查询语法树query进行搜索,得到结果TopScoreDocCollector。
以上便是Lucene API函数的简单调用。然而当进入Lucene的源代码后,发现Lucene有很多包,关系错综复杂。但是通过下图,我们可以发现,Lucene的个源码模块,都是对普通索引和搜索过程的一种实现。
- Lucene的analysis模块主要负责语法分析和语言处理形成term;
- Lucene的index模块主要负责索引的创建,里面有IndexWriter;
- Lucene的Store模块主要负责索引的读写;
- Lucene的queryParser主要负责语法分析;
- Lucene的search模块主要负责对索引的搜索;
- Lucene的similarity模块主要负责相关性打分的实现;
3. Lucene的索引文件格式
Lucene的索引里面存储了些什么,如何存放的,即Lucene的索引文件格式,是读懂Lucene源代码的一把钥匙。
Lucene的索引过程,就是按照全文检索的基本过程,将倒排表携程此文件格式的过程;
Lucene的搜索过程,就是按照此文件格式将索引进去的信息读取出来,然后计算每篇文档打分过程;
3.1. 基本概念
下图是Lucene生成的索引的一个实例:
Lucene的索引结构是有层次结构的,主要分为以下几个层次:
3.1.1. 索引(Index)
在Lucene中一个索引存放在一个文件夹中,如上图,同一个文件夹中的所有文件构成一个Lucene索引。
3.1.2. 段(Segment)
- 一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并;
- 如上图,具有相同前缀文件的属同一个段,图中共有两个段“_0”和“_1”;
- Segments.gen和segments_5是段的元数据文件,即他们保存了段的属性信息;
3.1.3.文档(Document)
文档是我们创建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档;新添加的文档是单独保存在一个新生成的段中,随着段的合并,不同的文档合并到同一个段中;
3.1.4.域(Field)
一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等。都可以保存在不同的域里;不同域的索引方式可以不同。
3.1.5. 词(Term)
词是索引的最小单位,是经过语法分析和语言处理后的字符串。
3.2. 正向信息和反向信息
在Lucene的索引结构中,及保存了正向信息,也保存了反向信息;
3.2.1. 正向信息
- 按层次保存了从索引,一直到词的包含关系:索引->段->文档->域->词;
- 也即此索引包含了那些段,每个段包含了那些文档,每个文档包含了那些域,每个域包含了那些词;
- 既然是层次结构,则每个层次都保存了本层次的信息以及下一层次的元信息,也即属性信息,比如一本书介绍中国地理的书,应该首先介绍中国地理的概况,以及中国包含多少个省,每个省介绍本省的基本概况及包含多少个市,每个市介绍本市的基本概况以及包含多少个县,每个县具体每个县的具体情况。
如上图,包含正向信息的文件有:
- Segments_N保存了此索引包含多少个段,每个段包含多少篇文档;
- Xxx.fnm保存了此段包含了多少个域,每个域的名称及索引方式;
- xxx.fdx,xxx.fdt保存了此段包含的所有文档,每篇文档包含了多少个域,每个域保存了那些信息。
- xxx.tvx,xxx.tvd,xxx.tvf保存了此段包好了多少文档,每篇文档包含了多少域,每个域包含了多少词,每个词的字符串,位置等信息;
3.2.2. 反向信息
保存了词典到倒排表的映射:词—>文档;
如上图,包含反向信息的所有文件有:
- Xxx.tis,xxx.tii保存了词典,也即此段包含的所有的词按字典顺序的排序;
- xxx.frq保存了倒排表,也即包含每个词的文档ID列表;
- xxx.prx保存了倒排表中每个词在包含此词的文档中的位置;
3.3. 基本类型
Lucene索引文件中,用一下基本类型来保存信息:
- Byte—是最近本的类型,长8位;
- UInt32—由四个byte组成;
- UInt64—由八个byte组成;
- VInt—变长的整数类型,他可能包含多个byte,对于每个byte的8位,其中后7位表示数值,最高1位表示是否还有一个byte,0表示没有,1 表示有;越前面的byte表示数值的低位,越后面的byte表示数值的高位;
- Chars—是UTF8编码的一些列byte;
- String—一个字符串首先是一个VInt来表示词字符串包含的字符的个数,接着便是UTF8编码的字符序列chars。
3.4. 具体格式
上面曾经提到过,Lucene保存了从index到segment到document到field到term的正向信息,也包含了从term到document映射的反向信息,还有其他一些Lucene特有的信息。
3.4.1.正向信息
Indexàsegments(segments.gen,segments_N)àfield(fnm,fdx,fdt)àterm(tvx,tvd,tvf)
上面的层次结构不是十分的准确,因为segments.gen和segments_N保存的是段(segment)的元数据信息(metadata),其实是每个Index一个的,而段的真正的数据信息,是保存在域(field)和词(term)中的。
1)段的元数据信息(segments_N)
一个索引可以同时保存在多个segments_N(至于如何存在多个segments_N,在描述完详细信息之后会举例说明),然而当我们要打开一个索引的时候,我们必须选择一个来打开,当如何选择哪个segments_N呢?
Lucene采用以下规则:
其一,在所在的segments_N中选择N最大的一个。基本逻辑参照segmentInfos.getCurrentSegmentGeneration(file[] files),其基本思路就是在所有以segments开头,并且不是segments.gen的文件中,选择N最大的一个作为genA。
其二,打开segments.gen,其中保存了当前的N值。其格式如下,读取出版本号,然后再读出两个N,如果两者相等,则作为genB。
其三,在上述得到的genA和genB中选择最大的那个作为当前的N,方才打开segments_N文件。
2) 域(field)的元数据信息(.fnm)
一个段(segment)包含多个域,每个域都有一些元数据信息,保存在.fnm文件中;
3)域(field)的数据信息(.fdt .fdx)
域数据文件(fdt):
- 真正保存存储域(stored field)信息的是fdt文件
- 在一个段(segment)中总共有segment size篇文档,所以fdt文件中共有segment size个项,每一项保存一篇文档的域的信息
- 对于每一篇文档,一开始是一个fieldcount,也即此文档包含的域的数目,接下来是fieldcount个项,每一项保存一个域的信息;
- 对于一个域,fieldnum是域号,接着是一个8位的byte,最低一位表示此域是否分词,倒数第二位表示此域是保存字符串还是一个二进制数据,倒数第三位表示此域是否被压缩,再接下来就是存储域,比如new Field(“title”,”lucene in action”,Field.store.Yes,…),则此处保存的就是“Lucene in action”这个字符串。
域索引文件(fdx):
- 由域数据文件格式我们知道,每篇文档包含的域的个数,每个存储的值都是不一样的,因而域数据文件中segment size篇文档,每篇文档占用的大小也不一样的,那么如何在fdt中辨别每一篇文档的其实地址和终止地址呢?如何能够快速的找到第n篇文档的存储域的信息呢?就要借助索引文件。
- 域索引文件也共有segment size项,每篇文档都有一个项,每一项都是一个long,大小固定,每一项都是对应在fdt文件中的其实地址的偏移量,这样如果我们想找到第N篇文档的存储域的信息,只要在fdx中找到第n项,然后按照取出的long作为偏移量,就可以在fdt文件中找到对应的存储域的信息。
4)词向量(term vector)的数据信息(.tvx,.tvd,.tvf)
词向量信息是从索引(index)到文档(document)到域(field)到词(term)的正向信息,有了词向量信息,我们就可以得到一篇文档包含那些词的信息。
词向量索引文件(tvx):
- 一个段(segment)包含N篇文档,此文件就有N项,每一项代表一篇文档;
- 每一项包含两部分信息:第一部分是词向量文档文件(tvd)中此文档的偏移量;第二部分是词向量域文件(tvf)中此文档的第一个域的偏移量。
词向量文档文件(tvd):
- 一个段包含N篇文档,此文件就有N项,每一项包含了此文档的所有的域的信息;
- 每一项首先是此文档包含的域的个数NumFields,然后是一个NumFields大小的数组,数组的每一项是域号。然后是一个(numfields – 1)大小的数组,有前面我们知道,每篇文档的第一个域在tvf中的偏移量在tvx文件中保存,而其他(numfields – 1)个域在tvf中的偏移量就是第一个域的偏移量加上这个(numfields – 1)个数组的每一项的值。
词向量域文件(tvf):
- 此文件包含了此段中的所有域,并不对文档做区分,到底第几个域到第几个域是属于那篇文档,是由tvx中的第一个域的偏移量以及tvd中的(numfields – 1)个域的偏移量来决定的。
- 对于每一个域,首先此域包含的词的个数numterms,然后是一个8位的byte,最后一位是指定是否保存位置信息,倒数第二位是指定是否保存偏移量信息。然后是num terms各项的数组,每一项代表一个词,对于每一个词,有词的文本termText,词频TermFreq(也即此词在文档中出现的次数),词的位置信息,词的偏移量信息。
3.4.2.反向向量
反向向量是索引文件的核心,也即反向索引。反向索引包括两部分,左面的是词典(term dictionary),右面的是倒排表(posting list)。在Lucene中,这两部分是分文件存储的,词典是存储在tii,tis中的,倒排表有包含两部分,一部分是文档号及词频,保存在frq中,一部分是词的位置信息,保存在prx中。
1) 词典(tis—Term infos file)及词典索引(tii)信息
词典文件(tis):
- TermCount—词典中包含的总的词数;
- IndexInterval—为了加快对词的查询速度,也应用类似跳跃表的,假设IndexInterval为4,则在词典索引(tii)文件中保存第四个,第八个,第十二个词,这样可以加快在词典文件中查找词的速度。
- SkipInterval—倒排表无论是文档号及词频,还是位置信息,都是以跳跃表的结构存在的,SkipInterval是跳跃的步数;
- MaxSkipLevels—跳跃表是多层的,这个值指的是跳跃表的最大层数。
- TermCount个项的数组,每一项代表一个词,对于每一个词,以前缀后缀规则保存词的文本信息(prefixLength + suffix),词属于的域的域号(fieldNum),有多少篇文档包含此词(docFreq),此词的倒排表在frq,prx中的偏移量(freqDelta,proxDelta)此词的倒排表的跳跃表在frq中的偏移量(SkipDelta),这里之所以用Delta,是应用差值规则;
词典索引文件(tii):
- 词典索引文件是为了加快对词文件中词的查找速度,保存每隔IndexInterval个词。
- 词典索引文件是会被全部加载到内存中去的。
- IndexTermCount=TermCount / indexInterval:词典索引文件中包含的词数。
- IndexInterval同词典文件中的IndexInterval.
- SkipInterval同词典文件中的SkipInterval。
- maxSkipLevels同词典文件中的maxSkipLevels。
- IndexTermCount个项的数组,每一项代表一个词,每一项包含两部分,第一部分是词本身(termInfo),第二部分是在词典文件中的偏移量(IndexDelta)。假设IndexInterval为4,此数组中保存第4个,第8个,第12个词。。。
3.4.3.其他信息
1) 标准化因子文件(nrm)
为什么会有标准化因子呢?从前面的描述,我们知道在搜索过程中,搜索出的文档要按与查询语句的相关性排序,相关性大的打分(score)高,从而排在前面。相关性打分(score)使用向量空间模型(vector space model),在计算相关性之前,要计算Term Weight,也即某term相对于某document的重要性。在计算Term Weight时,主要有两个影响因素,一个是此Term在此文档中出现的次数,一个是此Term的普通程度。
2)删除文档文件(del)
3.5. 总体结构
图为Lucene索引文件的整体结构:
- 属于整个索引(Index)的Segment.gen,segment_n,保存的是段(segment)的元数据信息,然后分为多个segment保存数据信息,同一个segment有相同的前缀文件名;
- 对于每一个段,包含域信息,词信息,以及其他信息(标准化因子,删除文档);
- 域信息也包括域的元数据信息,在fnm中,域的数据信息,在fdx,fdt中;
- 词信息是反向信息,包括词典(tis,tii),文档号及词频率倒排表(frq),此位置倒排表(prx).