全局搜索——Lucene.Net与盘古分词的实现思路


一、Lucene.Net

1、Lucene.Net介绍:

Lucene.Net是一个C#开发的开源全文索引库(自带的有索引管理、分词、查询)

  • Lucene.Net.Index 提供索引管理,词组排序。
  • Lucene.Net.Search 提供查询相关功能。
  • Lucene.Net.Store 支持数据存储管理,主要包括I/O操作。
  • Lucene.Net.Util 公共类。
  • Lucene.Net.Documents 负责描述索引存储时的文件结构管理。
  • Lucene.Net.QueryParsers 提供查询语法。
  • Lucene.Net.Analysis 负责分析文本。

由于Lucene.Net自带的分词算法对于中文的分词不是很强大,所以我们在分词这块采用盘古分词

2、Lucene的索引的数据结构

索引底层也是采用类似二叉树的表的结构

在lucene中最基本的概念是索引(index),文档(document),域(field)和项(term),他们之间的基本关系是这样的:

一个索引包含一系列的文档;

一个文档是一系列域的集合

一个域是一系列命了名称的项的集合

一个项是一个字符串

或者这样理解,如果把整个看作是

索引——一个数据表,

域——这个数据表中的某一列

项——对应某一列中的字符串关键字。也就是我们通常意义上的字符串分词以后的关键字

文档——可以看成是一次插入到数据库中的所有数据的集合

所以说,如果相同的字符串在不同的域中肯定是作为不同的项来对待的,项(terms)被标成成为字符串对的形式:第一个字符串用来命名域(field),第二个用来表示该域下文本的值

倒排索引:索引文件为了使得以项(term)为基础的搜索更加有效而储存有所有项(term)的统计数字。Lucene的索引使用的是一种叫做倒排索引(inverted index)的索引方式,这是因为,这种索引方式是从特定的关键字可以列出所有包含它的文档,与通常方式的由文档列出所有关键字正好是反过来的,类似Mysql的索引(B+树的结构)

域的类型:在lucene中,域可能被储存,在这种情况下这些域下的文本被按照字面意义的按照通常方式存储下来,被倒排的域称为索引了的域。一个域可能既被存储又被索引

域中的内容可能被分词成为一个个的项来进行索引,也有可能整个域下的内容就作为一个项来进行索引。类比到SQL server中的情形:我们说的前一种情况可以类比到那种某一列由大段文本组成,然后我们对这一列进行全文索引,然后文本会被分词程序分成一个个的关键词。然后索引。后一种情况类比到某一列的数据类型为datetime或者干脆为int类型,有时候我们需要为这样的列进行索引比如说取出某时间点之前存入的所有数据等等。这个时候我们没有必要也不会去对这样的类型分词以后索引,而是对整个域中的内容索引。还有一种情况就是,域中的内容只分词以后进行索引,但是不进行储存。这样的情况可以对应到进行web搜索时的头部meta信息。对于这样的信息我们只需要索引,不需要进行储存

:段这个概念不好理解,官方的解释是这样的:lucene的索引可能由若干子索引(sub-index)或者段(segment)组成。每一个段都是一个相对独立的可以单独进行搜索的索引。lucene的索引是这样产生的:

1、为新加入到全文索引中的文档创建单独的段结构

2、合并已经存在的段

Lucene索引实现

Lucene的索引不是B+Tree组织的,而是倒排索引,Lucene的倒排索引由Term index,Team Dictionary和Posting List组成。

有倒排索引(invertedindex)就有正排索引(forwardindex),正排索引就是文档(Document)和它的字段Fields正向对应的关系:

DocID

name

sex

age

1

jack

18

2

lucy

17

3

peter

17

倒排索引是字段Field和拥有这个Field的文档对应的关系:

Sex字段:

[1,3]

[2]

Age字段:

18

[1]

17

[2,3]

如果我们查的是男,我们很容易把含有男的1和3条数据找出来

Jack,lucy或者17,18这些叫做term,而[1,3]就是posting list。Posting list就是一个int型的数组,存储了所有符合某个term的文档id。那么什么是Term index和Term dictionary?

如上,假设name字段有很多个term,比如:Carla,Sara,Elin,Ada,Patty,Kate,Selena

如果按照这样的顺序排列,找出某个特定的term一定很慢,因为term没有排序,需要全部过滤一遍才能找出特定的term。排序之后就变成了:Ada,Carla,Elin,Kate,Patty,Sara,Selena

这样就可以用二分查找的方式,比全遍历更快地找出目标的term。如何组织这些term的方式就是 Term dictionary,意思就是term的字典。有了Term dictionary之后,就可以用比较少的比较次数和磁盘读次数查找目标。但是磁盘的随机读操作仍然是非常昂贵的,所以尽量少的读磁盘,有必要把一些数据缓存到内存里。但是整个Term dictionary本身又太大了,无法完整地放到内存里。于是就有了Term index。Term index有点像一本字典的大的章节表。比如:

A开头的term ……………. Xxx页

C开头的term ……………. Xxx页

E开头的term ……………. Xxx页

如果所有的term都是英文字符的话,可能这个term index就真的是26个英文字符表构成的了。但是实际的情况是,term未必都是英文字符,term可以是任意的byte数组。而且26个英文字符也未必是每一个字符都有均等的term,比如x字符开头的term可能一个都没有,而s开头的term又特别多。实际的term index是一棵trie 树:

上图例子是一个包含 "A", "to", "tea", "ted", "ten", "i", "in", 和 "inn" 的trie树。这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。再加上一些压缩技术(想了解更多,搜索 Lucene Finite State Transducers),Term index的尺寸可以只有所有term的尺寸的几十分之一,使得用内存缓存整个term index变成可能。整体上来说就是这样的效果:

3、检索的过程

相关