Lucene笔记04

使用Lucene建立索引,有三个主要步骤。

提取文本。Lucene只能对纯文本建立索引,所以,任何需要建立索引的资料,都要进行过滤处理,从中提取到纯文本。比如对于Word和PDF,我们都要使用相关API将其中的纯文本提取出来,而对于XML和HTML,则意味着要过滤掉所有的tag。

文本分析。要建立索引,首先要将文本分解成一个个片段,一般就是单词,当然也可能是词组,句子等。分割好的东西,可能还要进行归一化处理,以确保最大程度上的检索能力,比如,全部变成小写字母,以后搜索的时候,就能忽略大小写。这个过程对于字母文字,有个步骤,就是回归原型,像英文、德文、法文这些我稍微有点了解的语言里,一般都有“数”,“格”,“态”的变化,而同一个词的变化形式,应该被视为是一个词,而不是不同的词。对于汉语这样的没有变形的语言,这方面就非常方便了,但是汉语却有着另一个不方便的地方,就是汉语的最小单位不是字,而是词。也即汉语需要进行分词处理。英文单词使用空格分隔,分词要简单得多得多。除却这些步骤,还有一个共同的步骤就是删除stop words,简单说就是无意义词,一般来说就是数词,量词,助词,介词,代词等等虚词。

将索引写入磁盘。Lucene将分析好的文本使用一种叫做倒排索引的数据结构写入到磁盘中。倒排索引(inverted index)的建立,完全是为了搜索的方便。如果说,“正排索引”可以回答你一个问题,“这个文档中,包含了哪些关键词?”,那么“倒排索引”回答了你一个相反的问题,就是“哪些文档,包含了关键词X?”。倒排索引是当今所有主流搜索引擎的核心结构,而这些搜索引擎之所以不同,是因为在建立倒排索引时所附加的独特的参数,比如著名的Google PageRank。这些参数决定了最终搜索结果的排序。

笔记02中,写了一些建立索引的基本元件,但是没有详细研究。这里从逻辑上来看一下Lucene的索引。Lucene的索引从逻辑上看是一个整体,内部的结构却很复杂,可以很方便的合并,删除,而且并发访问的能力很强。索引的内部,最小的单位是字段(Field),然后一些字段的集合是文档(Document)。这个文档和我们对其建立索引的那个“文档”是不同的,这是Lucene索引内部组织的一种形式,按照我的理解,就是一组相关的字段,规整在一起,就是一个Lucene Document。举个例子,一个Lucene Document包含标题,作者,日期,关键字,内容五个字段,具体字段的数量,应该是在建立索引的时候决定的,可以根据需要添加足够多的字段。比如还可以添加网址,分类信息等等。事实上,在建立索引完毕后,我们从索引中搜索出来的最终结果,是Lucene Document,而不是原本我们对其建立索引的文件。这些Document通过其内部字段信息,和真正的文件建立关联,比如我们在Google中,搜到的结果,都带有一个超链接指向真正存在于互联网某个角落的网页。

字段是一种数据结构,包含一个名称(name),和对应的值(value),可以看作是一种键值对。字段的值可以是普通文本,比如一个网页的全部正文部分,在构造一个Field的时候,这样的值由java.lang.String或者java.io.Reader封装,这样的字段在建立索引时,会经历上文中第二个步骤——文本分析;字段值也可以是原子化的关键字,这样的值,在建立索引的时候,不会被分析,这样的值往往是用来标识一个文件的日期,url等信息。字段是否需要存储到索引中,是可选的,存储的字段在搜索时也会包含于搜索结果中。

根据一个字段是否需要分析,是否需要索引,是否需要存储,字段应该一共分成八种。但是有些种类是没有意义的,比如需要分析,但是不需要索引的字段,实际上是不存在的,所以,实际常用的字段只有四种类型。

Keyword——这类字段不会被分析,但是会被索引并且会被保存。这类字段的完整性需要收到保护,比如网址,电话号码,日期,人名等等。

UnIndexed——这类字段不会被分析,也不会被索引,但是会被保存。这种字段一般用来存储你需要显示在搜索结果中的内容,但是你往往不需要直接搜索它们的值。我个人对这个种类是存在疑问的,我基本上想不出来有哪些信息满足这种特征的。搜索引擎发展到今天,我们可能在Google搜索框中敲入任何东西,曾经看到一篇文章,里面有句话,“……你可能无法想象,有多么高的比例的人,在使用Google的时候,在搜索框中直接打入一个完整的网址……”,确实是这样,像我爸这类用户,把Google设成首页,完全无视浏览器的地址栏,跑题了。如果说非有这么一种字段,我想,可能是标识了这个搜索结果的属性的信息。比如,你搜索“Charles”,返回结果里有一些信息,说“性别:男”,“物种:人类”,“居住地:地球”,这种信息应该满足条件了,但是不是现实生活中的例子,呵呵,搜索引擎要真那么恐怖,某些人就惨了。

UnStored——这类字段与Unindex正好完全相反。会被分析,并且索引,但是不会被保存。比如,一本书的全部内容。一个网页的全部正文。

Text——这类字段会被分析,索引,如果是纯文本,也会被存储。符合这个种类的例子,我能想到的例子,比如一篇论文的摘要。这样的文本段落不但有高密度的关键字,而且几乎可以完整的概述一个文件的内容。

上述的四个种类,在早期的Lucene版本中,本身就有对应的类,构造字段的时候直接使用上面的名字构造相应的对象即可。但是在最新版本的Lucene中,使用了一种更为统一的形式,也即只有Field一个类,然后使用一些参数来描述这个字段的属性,通过参数组合,可以组合出各种类别,甚至那四种不存在的类别理论上也是可以组合出来。

现在的Field构造函数原型是如下样子的:

1
public Field(String name, String value, Store store, Index index) 

我想,有了上面的分析,这个构造函数应该是相当容易理解了。一开始就是因为理解得不透彻,导致我自己犯了很低级的错误。然后,根据新的构造函数的原型,来总结一下上面的内容。

col:Store, row:Index

YES

NO

COMPRESS

NO

存储,但是不建立索引,当然也就不分析。这样的字段无法搜索,但是会出现在搜索结果中。

无意义。引发Illegal Argument Exception。

基本等同于YES,但是外加了压缩。对于较长的文本和二进制的字段,应该选用这个参数。计算量更大。

ANALYZED

分析,索引,存储。

分析,索引,不存储。

分析,索引,压缩存储。

NOT_ANALYZED

不分析,但是索引,存储。

不分析,直接索引,但是不存储。

不分析,但是索引,压缩存储。

NOT_ANALYZED_NO_NORMS

(高级)

(高级)

(高级)

ANALYZED_NO_NORMS

(高级)

(高级)

(高级)

呵呵,事实上,Lucene内部的字段种类,一共有十四种之多,像上表描述的一样。从这张表中,能发现,一个实际的,有生产能力的系统,远比理论模型要复杂,细致,考虑的细节更多。

实际上,构造的Field的时候,还有一个可选参数。就是TermVector。所谓的TermVector,字面理解就是关键字向量。通俗点说,就是实际上该字段中,被索引的词语的一个列表,还有跟每个词关联的信息,比如位置(Position,第几个词)或者偏移量(Offset,第几个字节,辅助高亮显示)。这也就暗示着一件事情,如果一个字段没有被索引,那么也不能指定其带有TermVector参数。TermVector有五种取值:

NO——默认值。不保留信息。

YES——保留词及该词出现的次数。

WITH_POSITIONS

WITH_OFFSETS

WITH_POSITIONS_OFFSETS

可以为每个Field指定TermVector选项,但是最后每个Document只维护一个Term Vector。