lucene索引建立

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

一、Lucene 数据源组织结构
举个列子,比如我们想要检索文件的路径,修改时间和内容。

我们可以创建三个Field对象分别存储这三种数据:
我们可以创建一个Document对象,加入这三个Field,来表示一个文档文件需要被检索的三种数据。

总结:下面的图很清楚的说明Lucene的数据源表示形式
Lucene首先将这三个数据源在内存中组织成Document、Field 如下表:
Document 和Field在Lucene中的作用是巨大的。

我们都知道Lucene可以对任何形式的数据源建立索引,比如字符串、纯文本、XML、HTML等数据形式。

怎么多杂乱无章的数据
必须组织成统一有效的结构才能更好的处理,Document / Field 无疑做到了这一点。

二、IndexWrite索引创建
我们对content目录中1.txt、2.txt、3.txt这三个英文文档的文件名,路径,内容建立索引。

代码如下:
其中,IndexWriter索引器用来创建索引。

Document和Field类表示这三个文档的结构如下:
1、建立索引的方法:addDocument(Document)
该方法是用来创建索引的。

我们进一步看看这个方法的源码:
其中docWriter.addDocument(doc,analyzer);调用了DocumentWriter类来创建一个Document 对象的索引。

三、DocumentWriter处理流程
DocumentsWriter是由IndexWriter调用来负责对多个document建立索引的核心类,但整个索引过程并不是由一个对象来完成的。

而是有一系列的对象组成的处理链(IndexingChain)来完成的(这个过程就像流水线生产汽车)。

下面是DocumentWriter开始建立索引的源代码。

1 第一车间——DocFieldProcessorPerThread
DocFieldProcessorPerThread类是索引创建处理链的第一步。

其基本任务:将document对象原料中所有相同名字的field合并成一个DocFieldProcessorPerThread对象,然后更新FieldInfo信息,最后对不同名字的Field构成一个DocFieldProcessorPerThread[]对象数组。

这个数组就是下一个车间DocInverterPerField要加工的原料了。

用个图例来说明一下DocFieldProcessorPerThread类所做的工作。

原料:Document doc1 (为了说明相同Field的合并工作,我们加了一个相同名字,值不同的content Field)
注意,上图中的DocFieldProcessorPerField的next域都指向了null。

其实,如果有Field1的名字name1与Field2的名字name2满足 HashCode(name1)=HashCode(name2)
&& !name1.equals(name2) 的情况下。

Field2所构成的DocFieldProcessorPerField对象将加在Field1所构成的DocFieldProcessorPerField对象的next链表后面。

这种组织方法便于我们在后面要讲到的建立倒排索引的处理。

总结:DocFieldProcessorPerThread类的作用就是把Document对象加工成DocFieldProcessorPerField[] (上图黄色区域) 。

然后把每个DocFieldProcessorPerThread.Fieldable[] (上图红色区域)交给第二车间DocInverterPerField的processFields方法来完成了。

2 第二车间——DocInverterPerField
DocInverterPerField 负责对DocFieldProcessorPerThread对象的Fieldable[]数组的内容建立倒排索引,也就是处理同名字的所有Field。

但实际上这个类主要解决的是前期工作,比如分词,统计位置信息等。

倒排索引结构的核心的工作由TermsHashPerField和FreqProxTermsWriterPerField (第三车间)来完成。

这两个类将在后面的专题中再提及。

DocInverterPerField 核心方法是processFields(Fieldable[] fields)。

它负责这几个方面的工作:
(1)将field的value值切分成一个个term
(2)调用FieldInvertState类来存储和统计当前field的所有term出现的位置position
和offset信息,并计算该field的boost分值,为所有相同名字的fields的boost与文档的boost的乘积。

(3) 调用TermsHashPerField和 FreqProxTermsWriterPerField把每个term加入倒排索引结构。

我们用上一节的doc1的例子来查看这个stream的结果,其中doc1通过上一节加工成了DocFieldProcessorPerThread fields[]数组。

而fields[0]就是指doc1中名字为cotent 的field集合,这个集合有两个content field。

(1) TermAttribute:表示token的字符串信息。

比如"I'm"
(2) TypeAttribute:表示token的类别信息(在上面讲到)。

比如 I'm 就属于<APOSTROPHE>,有撇号的类型
(3) OffsetAttribute:表示token的首字母和尾字母在原文本中的位置。

比如 I'm 的位置信息就是(0,3)
(4) PositionIncrementAttribute:这个有点特殊,它表示tokenStream中的当前token
与前一个token在实际的原文本中相隔的词语数量。

总结,下图展示了DocInverterPerField的作用。

它会把不需要分词的field以红色方框的结构(field value)传给TermsHashPerField和 FreqProxTermsWriterPerField来建立索引。

而把需要分词的content field变成一个个蓝色方框的结构(token && position)来建立索引,接下来就是对token建立倒排索引的过程了。

注意,上图蓝色方框的箭头并不是指DocInverterPerField会把他们建立成链表结构。

事实上,这些箭头只是为了表明一个个token依次被 TermsHashPerField加入索引结构的。

另外,相同名字的field中的词语会依次处理,就如同上面fields[0]和fields[1]。

3 第三车间—— TermsHashPerField &FreqProxTermsWriterPerField
TermsHashPerField和FreqProxTermsWriterPerField负责将token信息(字符串内容termTest,所在文档编号docID,所在文档中的位置position,所在文档中的词频frequence)添加到索引的Hash表结构(postingsHash)中。

事实上,这些信息并不是直接存放在Hash
表中的,而是存放在三个很重要的数据缓存池中(CharBlockPool、IntBlockPool、ByteBlockPool)。

而postingsHash中存放的只是数据在三个数据池中的地址偏移。

1) CharBlockPool: 存储token的字面信息
2) ByteBlockPool: 存储token所在文档编号,位置和词频信息
3) IntBlockPool: 存储对应的ByteBlockPool中slice的位置
倒排索引结构是 token -> posting list的形式,而文档结合的所有token组成了一个Dictionary。

TermsHashPerField类的作用就是建立这样一个倒排索引结构postingHash。

其中 postingHash[](哈希数组)是以token的字面值作为关键字的,相当于Dictionary。

而 postingHash的每一个元素都指向了PostingList对象,这个对象就是用来存储指定token所对应的posting list信息(包括docID,freq,position)。

实际上,真正的信息是存储在三大数据池中的,但 PostingList对象只存储三大数据池中的地址偏移。

我们通过上面的代码可以发现: TermsHashPerField已经把token的字面值存储在CharBlockPool 中了,并且在ByteBlockPool中分配好的存储空间,并将地址偏移记录到了IntBlockPool 中了。

接下来要做的就是把 token所对应的docID,freq,position的信息通过FreqProxTermsWriterPerField 的方法写入ByteBlockPool。

4 索引数据池存储细节
倒排索引(token->posting list)表的数据信息在内存中并不是直接存储在postingsHash
中的,而是存放在三大数据缓冲池中——CharBlockPool,ByteBlockPool,IntBlockPool。

这三个池均都由若干个固定长度的buffer数组构成。

DocumentsWriter对它们进行管理和维护(包括分配新的块或者回收不用的块的操作),以达到节省内存和提高效率的作用。

三大索引数据池
1、 token字符数据缓冲池—— CharBlockPool
CharBlockPool 用于存储token的字符串信息。

比如 token="lucene"。

其在内存中的表示其实就是多个buffer[]组成的缓冲池buffers[][]。

缓冲池初始大小为10*16384个字符空间,每一次可扩展1.5倍。

2、 token的docID,词频和位置信息缓冲池—— ByteBlockPool
ByteBlockPool 用于存储token所在的文档,词频和位置信息。

其内存结构与CharBlockPool相同,也是一个buffers[][]二维数组。

但是ByteBlockPool缓冲池中的buffer采用了分片(slice)分配方式。

每片(slice)的大小由nextLevelArray和levelSizeArray来确定。

3、 ByteBlockPool 片地址信息缓冲池—— IntBlockPool
IntBlockPool的职责就是用来记录ByteBlockPool中slice在buffer中的位置序号。

每一个token都会有两种信息同时存储在ByteBlockPool的slice中。

也就是说每一次ByteBlockPool都会同时分配两个相同大小的slice,一个用来存储docID+词频;另一个用来存储位置信息。

而这两个块的初始位置序号都会同时记录在IntBlockPool中的。

在这里,我们要详细探究倒排索引表中的数据信息是如何存储在这些数据池中的?这些重要信息包括:token字符串、所在文档词频、所在文档的docID、所在文档的位置。

索引存储的数据细节
5 IndexWriter的关闭
IndexWriter索引器创建内存索引的整体流程在前几篇文章中已经详细阐述了,当我们利用IndexWriter创建完内存索引表之后,剩下的工作就只剩下关闭IndexWriter了。

IndexWriter在关闭的时候除了清理内存中的对象之外,还有一个非常重要的工作,就是把内存要存储的信息(需要保存的Fields信息,倒排索引表等)写入Lucene的磁盘索引文件。

相关文档
最新文档