当前位置:文档之家› lucene入门体会

lucene入门体会

lucene入门体会
lucene入门体会

前沿

写这点文字是面向lucene初中级用户,站在一个编辑加工的角度,对lucene进行解释,使抽象的概念更容易被大家理解,也尽可能节省大家搜集资料之苦,在这里广纳资源,采其精华,供大家分享。

一、背景简介

Lucene 是一个基于 Java 的全文检索工具包,你可以利用它来为你的应用程序加入索引和检索功能。Lucene 目前是著名的 Apache Jakarta 家族中的一个开源项目https://www.doczj.com/doc/d06974470.html,/lucene/,很多Java项目都使用了Lucene作为其后台的全文索引引擎,比较著名的有:

?Jive:WEB论坛系统;

?Eyebrows:邮件列表HTML归档/浏览/查询系统,本文的主要参考文档“TheLucene search engine: Powerful, flexible, and free”作者就是EyeBrows系统的主要开发者之一,而EyeBrows已经成为目前

APACHE项目的主要邮件列表归档系统。

?Cocoon:基于XML的web发布框架,全文检索部分使用了Lucene ?Eclipse:基于Java的开放开发平台,帮助部分的全文索引使用了Lucene

?IBM社区:网站的搜索功能。

对于中文用户来说,最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍,你会了解到由于Lucene良好架构设计,对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的

支持。

二、demo运行

1、demo的配置

目前最新的版本是lucene-2.2.0 ,下载解压后(或附件包中)看到luceneweb.war,lucene-demos-2.2.0.jar,lucene-core-2.2.0.jar三个文件。

0).自持demo运行:

在环境变量classpath里面配置加入lucene-demos-2.2.0.jar,lucene-core-2.2.0.jar的存放路径。手工在C盘根目录下面建立目录luceneindex(存放索引文件),demo(用来测试的文件,放入一些html、text 文件)。放到其他位置也可以,只是在下面执行命令的路径处作相应更改。转到cmd工作模式下,

打入命令:(创建测试文件的索引文件)

java org.apache.lucene.demo.IndexHTML –create –index ./c://luce neindex ./ c://demo

OK!如果看到

adding /usr/local/man/man1/mysql.1

...........

adding /usr/local/man/man1/cvs.1

1614 total milliseconds

就会将demo目录下的文件建立索引,并将索引文件存到luceneindex目录下面。

打入命令:(检索索引文件)

java org.apache.lucene.demo.SearchFiles

出现Query:

OK!Lucene自待的demo运行成功,输入检索值,如果你的测试文档中有相关关键字即可出现如下类似结果

Query: password

Searching for: password

7 total matching documents

0. /usr/local/man/man1/mysql.1

......

6. /usr/local/man/man1/mysqlshow.1

1)、luceneweb.war的运行:

luceneweb.war复制到tomcat的webapp目录下,假定tomcat装在$TOMCATHOME目录下,具体应用时用真实的目录替换$TOMCATHOME。

cd $TOMCATHOME/webapps

mkdir lucenedb

cd lucenedb

java org.apache.lucene.demo.IndexHTML -create -index

$TOMCAT/webapps/lucenedb ../examples<--用相对路径“..”,一来指明被索引的文件的位置,二来用来显示被索引文件的URL,因为检索的jsp程序在luceneweb子目录下.examples可用其它的真实应用的目录名来替换cd ..

cp

~/lucene-1

.2/lucenew

eb.war . <--luceneweb.war在你解压缩生成的lucene-1.2目录下

../bin/shudown.sh

../bin/startup.sh

然后通过客户端访问https://www.doczj.com/doc/d06974470.html,:8080/luceneweb,如果顺利浏览器应出现右边所示的内容。.再到服务器端

cd luceneweb

vi configuration.jsp <--将indexLocation 的值改为"$TOMCATHOME/webapps/lucenedb";

cd ..

jar -ur luceneweb.war luceneweb

再到客户端,刷新刚才的页面,然后就可以输入单词进行检索了。

2、原理分析

0)设有两篇文章1和2

文章1的内容为:Tom lives in Guangzhou,I live in Guangzhou too.

文章2的内容为:He once lived in Shanghai.

1)全文分析:由于lucene是基于关键词索引和查询的,首先我们要取得这两篇文章的关键词,通常我们需要如下处理措施

a.我们现在有的是文章内容,即一个字符串,我们先要找出字符串中的所有单词,即分词。英文单词由于用空格分隔,比较好处理。中文单词间是连在一起的需要特殊的分词处理。

b.文章中的”in”, “once” “too”等词没有什么实际意义,中文中的

“的”“是”等字通常也无具体含义,这些不代表概念的词可以过滤掉

c.用户通常希望查“He”时能把含“he”,“HE”的文章也找出来,所以所有单词需要统一大小写。

d.用户通常希望查“live”时能把含“lives”,“lived”的文章也找出来,所以需要把“lives”,“lived”还原成“live”

e.文章中的标点符号通常不表示某种概念,也可以过滤掉

在lucene中以上措施由Analyzer类完成

经过上面处理后

文章1的所有关键词为:[tom] [live] [guangzhou] [i] [live] [guangzhou] 文章2的所有关键词为:[he] [live] [shanghai]

2) 倒排索引:有了关键词后,我们就可以建立倒排索引了。上面的对应关系是:“文章号”对“文章中所有关键词”。倒排索引把这个关系倒过来,变成:“关键词”对“拥有该关键词的所有文章号”。文章1,2经过倒排后变成

关键词文章号

guangzhou 1

he 2

i 1

live 1,2

shanghai 2

tom 1

通常仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现次数和出现的位置,通常有两种位置:a)字符位置,即记录该词是文章中第几个字符(优点是关键词亮显时定位快);b)关键词位置,即记录该词是文章中第几个关键词(优点是节约索引空间、词组(phase)查询快),lucene 中记录的就是这种位置。

加上“出现频率”和“出现位置”信息后,我们的索引结构变为:

以live 这行为例我们说明一下该结构:live在文章1中出现了2次,文章2中出现了一次,它的出现位置为“2,5,2”这表示什么呢?我们需要结合文章号和出现频率来分析,文章1中出现了2次,那么“2,5”就表示live在文章1中出现的两个位置,文章2中出现了一次,剩下的“2”就表示live是文章2中第 2个关键字。

以上就是lucene索引结构中最核心的部分。我们注意到关键字是按字符顺序排列的(lucene没有使用B树结构),因此lucene可以用二元搜索算法快速定位关键词。

实现时 lucene将上面三列分别作为词典文件(Term Dictionary)、频率文件(frequencies)、位置文件 (positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。

Lucene中使用了field的概念,用于表达信息所在位置(如标题中,文章中,url 中),在建索引中,该field信息也记录在词典文件中,每个关键词都有一个field信息(因为每个关键字一定属于一个或多个field)。

为了减小索引文件的大小,Lucene对索引还使用了压缩技术。首先,对词典文件中的关键词进行了压缩,关键词压缩为<前缀长度,后缀>,例如:当前词为“阿拉伯语”,上一个词为“阿拉伯”,那么“阿拉伯语”压缩为<3,语>。其次大量用到的是对数字的压缩,数字只保存与上一个值的差值(这样可以减小数字的长度,进而减少保存该数字需要的字节数)。例如当前文章号是16389(不压缩要用3个字节保存),上一文章号是16382,压缩后保存7(只用一个字节)。注意是“上一个词”。由于词典是按顺序排列的,这种压缩方法的效果会非常显著。

下面我们可以通过对该索引的查询来解释一下为什么要建立索引。

假设要查询单词“live”,lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。

而用普通的顺序匹配算法,不建索引,而是对所有文章的内容进行字符串匹配,这个过程将会相当缓慢,当文章数目很大时,时间往往是无法忍受的。

全文检索框架的实现机制:

Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表==>记录==>字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。

比较一下Lucene和数据库:

全文检索≠ like "%keyword%"

由于数据库索引不是为全文索引设计的,因此,使用like "%keyword%"时,数据库索引是不起作用的,在使用like查询时,搜索过程又变成类似于一页页翻书的遍历过程了,所以对于含有模糊查询的数据库服务来说,LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配:like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。

通常比较厚的书籍后面常常附关键词索引表(比如:北京:12, 34页,上海:3,77页……),它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样,想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高,另外一个原因是它是排好序的。对于检索系统来说核心是一个排序问题。

所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制,将数据源(比如多篇文章)排序顺序存储的同时,有另外一个排好序的关键词列表,用于存储关键词==>文章映射关系,利用这样的映射关系索引:[关键词==>出现关键词的文章编号,出现次数(甚至包括位置:起始偏移量,结束偏移量),出现频率],检索过程就是把模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程。从而大大提高了多关键词查询的效率,所以,全文检索问题归结到最后是一个排序问题。

由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题,这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实

现了传统数据库不擅长的全文索引机制,并提供了扩展接口,以方便针对不同应用的定制。

可以通过一下表格对比一下数据库的模糊查询:

3、重要的几个概念解释

Lucene中最基础的概念是索引(index),文档(document.,域(field)和项(term)。

索引包含了一个文档的序列。

·文档是一些域的序列。

·域是一些项的序列。

·项就是一个字串。

存在于不同域中的同一个字串被认为是不同的项。因此项实际是用一对字串表示的,第一个字串是域名,第二个是域中的字串。

倒排索引

为了使得基于项的搜索更有效率,索引中项是静态存储的。Lucene的索引属于索引方式中的倒排索引,因为对于一个项这种索引可以列出包含它的文档。这刚好是文档与项自然联系的倒置。

域的类型

Lucene中,域的文本可能以逐字的非倒排的方式存储在索引中。而倒排过的域称为被索引过了。域也可能同时被存储和被索引。

域的文本可能被分解许多项目而被索引,或者就被用作一个项目而被索引。大多数的域是被分解过的,但是有些时候某些标识符域被当做一个项目索引是很有用的。

段(Segment)

Lucene索引可能由多个子索引组成,这些子索引成为段。每一段都是完整独立的索引,能被搜索。索引是这样作成的:

1. 为新加入的文档创建新段。

2. 合并已经存在的段。

搜索时需要涉及到多个段和/或者多个索引,每一个索引又可能由一些段组成。

文档号(document.nbspNumber)

内部的来说,Lucene用一个整形(interger)的文档号来指示文档。第一个被加入到索引中的文档就是0号,顺序加入的文档将得到一个由前一个号码递增而来的号码。

注意文档号是可能改变的,所以在Lucene外部存储这些号码时必须小心。特别的,号码的改变的情况如下:

·只有段内的号码是相同的,不同段之间不同,因而在一个比段广泛的上下文环境中使用这些号码时,就必须改变它们。标准的技术是根据每一段号码多少为每一段分配一个段号。将段内文档号转换到段外时,加上段号。将某段外的文档号转换到段内时,根据每段中可能的转换后号码范围来判断文档属于那一段,并减调这一段的段号。例如有两个含5个文档的段合并,那么第一段的段号就是0,第二段段号5。第二段中的第三个文档,在段外的号码就是8。

·文档删除后,连续的号码就出现了间断。这可以通过合并索引来解决,段合并时删除的文档相应也删掉了,新合并而成的段并没有号码间断。

绪论

索引段维护着以下的信息:

·域集合。包含了索引中用到的所有的域。

·域值存储表。每一个文档都含有一个“属性-值”对的列表,属性即为域名。这个列表用来存储文档的一些附加信息,如标题,url或者访问数据库的一个ID。在搜索时存储域的集合可以被返回。这个表以文档号标识。·项字典。这个字典含有所有文档的所有域中使用过的的项,同时含有使用过它的文档的文档号,以及指向使用频数信息和位置信息的指针。

·项频数信息。对于项字典中的每个项,这些信息包含含有这个项的文档的总数,以及每个文档中使用的次数。·项位置信息。对于项字典中的每个项,都存有在每个文档中出现的各个位置。

·Normalization factors. For each field in each document.a value is stored that is multiplied into the score for hits on that field. 标准化因子。对于文档中的每一个域,存有一个值,用来以后乘以这个这个域的命中数(hits)。

·被删除的文档信息。这是一个可选文件,用来表明那些文档已经删除了。

接下来的各部分部分详细描述这些信息。

文件的命名(File Naming)

同属于一个段的文件拥有相同的文件名,不同的扩展名。扩展名由以下讨论的各种文件格式确定。

一般来说,一个索引存放一个目录,其所有段都存放在这个目录里,尽管我们不要求您这样做。

基本数据类型(Primitive Types)

Byte

最基本的数据类型就是字节(byte,8位)。文件就是按字节顺序访问的。其它的一些数据类型也定义为字节的序列,文件的格式具有字节意义上的独立性。

UInt32

32位无符号整数,由四个字节组成,高位优先。

UInt32 --> 4

Uint64

64位无符号整数,由八字节组成,高位优先。

UInt64 --> 8

VInt

可变长的正整数类型,每字节的最高位表明还剩多少字节。每字节的低七位表明整数的值。因此单字节的值从0到127,两字节值从128到16,383,等等。

VInt 编码示例

value

First byte

Second byte

Third byte

1

00000001 2

00000010 ...

127

01111111 128

10000000 00000001 129

10000001 00000001 130

10000010 00000001 ...

16,383

11111111 01111111 16,384

10000000 10000000 00000001

16,385

10000001

00000001

...

这种编码提供了一种在高效率解码时压缩数据的方法。

Chars

Lucene输出UNICODE字符序列,使用标准UTF-8编码。

String

Lucene输出由VINT和字符串组成的字串,VINT表示字串长,字符串紧接其后。

String --> VInt, Chars

索引包含的文件(Per-Index Files)

这部分介绍每个索引包含的文件。

Segments文件

索引中活动的段存储在Segments文件中。每个索引只能含有一个这样的文件,名为"segments".这个文件依次列出每个段的名字和每个段的大小。

Segments --> SegCount, SegCount

SegCount, SegSize --> UInt32

SegName --> String

SegName表示该segment的名字,同时作为索引其他文件的前缀。

SegSize是段索引中含有的文档数。

有一些文件用来表示另一个进程在使用索引。

·如果存在"commit.lock"文件,表示有进程在写"segments"文件和删除无用的段索引文件,或者表示有进程在读"segments"文件和打开某些段的文件。在一个进程在读取"segments"文件段信息后,还没来得及打开所有该段的文件前,这个Lock文件可以防止另一个进程删除这些文件。

·如果存在"index.lock"文件,表示有进程在向索引中加入文档,或者是从索引中删除文档。这个文件防止很多文件同时修改一个索引。

Deleteable文件

名为"deletetable"的文件包含了索引不再使用的文件的名字,这些文件可能并没有被实际的删除。这种情况只存在与Win32平台下,因为Win32下文件仍打开时并不能删除。

Deleteable --> DelableCount, DelableCount

DelableCount --> UInt32

DelableName --> String

段包含的文件(Per-Segment Files)

剩下的文件是每段中包含的文件,因此由后缀来区分。

域(Field)

域集合信息(Field Info)

所有域名都存储在这个文件的域集合信息中,这个文件以后缀.fnm结尾。

FieldInfos (.fnm) --> FieldsCount, FieldsCount

FieldsCount --> VInt

FieldName --> String

FieldBits --> Byte

目前情况下,FieldBits只有使用低位,对于已索引的域值为1,对未索引的域值为0。

文件中的域根据它们的次序编号。因此域0是文件中的第一个域,域1是接下来的,等等。这个和文档号的编号方式相同。

域值存储表(Stored Fields)

域值存储表使用两个文件表示:

1. 域索引(.fdx文件)。

如下,对于每个文档这个文件包含指向域值的指针:

FieldIndex (.fdx) --> SegSize

FieldvaluesPosition --> Uint64

FieldvaluesPosition指示的是某一文档的某域的域值在域值文件中的位置。因为域值文件含有定长的数据信息,因而很容易随机访问。在域值文件中,文档n的域值信息就存在n*8位置处(The position of document.nbspn's field data is the Uint64 at n*8 in this file.)。

2. 域值(.fdt文件)。

如下,每个文档的域值信息包含:

FieldData (.fdt) --> SegSize

DocFieldData --> FieldCount, FieldCount

FieldCount --> VInt

FieldNum --> VInt

Bits --> Byte

value --> String

目前情况下,Bits只有低位被使用,值为1表示域名被分解过,值为0表示未分解过。

项字典(Term Dictionary)

项字典用以下两个文件表示:

1. 项信息(.tis文件)。

TermInfoFile (.tis)--> TermCount, TermInfos

TermCount --> UInt32

TermInfos --> TermCount

TermInfo -->

Term -->

Suffix --> String

PrefixLength, DocFreq, FreqDelta, ProxDelta

--> VInt

项信息按项排序。项信息排序时先按项所属的域的文字顺序排序,然后按照项的字串的文字顺序排序。

项的字前缀往往是共同的,与字的后缀组成字。PrefixLength变量就是表示与前一项相同的前缀的字数。因此,如果前一个项的字是"bone",后一个是"boy"的话,PrefixLength值为2,Suffix值为"y"。

FieldNum指明了项属于的域号,而域名存储在.fdt文件中。

DocFreg表示的是含有该项的文档的数量。

FreqDelta指明了项所属TermFreq变量在.frq文件中的位置。详细的说,就是指相对于前一个项的数据的位置偏移量(或者是0,表示文件中第一个项)。

ProxDelta指明了项所属的TermPosition变量在.prx文件中的位置。详细的说,就是指相对于前一个项的数据的位置偏移量(或者是0,表示文件中第一个项)。

2. 项信息索引(.tii文件)。

每个项信息索引文件包含.tis文件中的128个条目,依照条目在.tis文件中的顺序。这样设计是为了一次将索引信息读入内存能,然后使用它来随机的访问.tis文件。

这个文件的结构和.tis文件非常类似,只在每个条目记录上增加了一个变量IndexDelta。

TermInfoIndex (.tii)--> IndexTermCount, TermIndices

IndexTermCount --> UInt32

TermIndices --> IndexTermCount

IndexDelta --> VInt

IndexDelta表示该项的TermInfo变量值在.tis文件中的位置。详细的讲,就是指相对于前一个条目的偏移量(或者是0,对于文件中第一个项)。

项频数(Frequencies)

.frq文件包含每一项的文档的列表,还有该项在对应文档中出现的频数。

FreqFile (.frq) --> TermCount

TermFreqs --> DocFreq

TermFreq --> DocDelta, Freq?

DocDelta,Freq --> VInt

TermFreqs序列按照项来排序(依据于.tis文件中的项,即项是隐含存在的)。

TermFreq元组按照文档号升序排列。

DocDelta决定了文档号和频数。详细的说,DocDelta/2表示相对于前一文档号的偏移量(或者是0,表示这是TermFreqs里面的第一项)。当DocDelta是奇数时表示在该文档中频数为1,当DocDelta是偶数时,另一个VInt(Freq)就表示在该文档中出现的频数。

例如,假设某一项在文档7中出现一次,在文档11中出现了3次,在TermFreqs中就存在如下的VInts序列:

15, 22, 3

项位置(Position)

.prx文件包含了某文档中某项出现的位置信息的列表。

ProxFile (.prx) --> TermCount

TermPositions --> DocFreq

Positions --> Freq

PositionDelta --> VInt

TermPositions按照项来排序(依据于.tis文件中的项,即项是隐含存在的)。

Positions元组按照文档号升序排列。

PositionDelta是相对于前一个出现位置的偏移位置(或者为0,表示这是第一次在这个文档中出现)。

例如,假设某一项在某文档第4项出现,在另一个文档中第5项和第9项出现,将存在如下的VInt序列:4, 5, 4

标准化因子(Normalization Factor)

.nrm文件包含了每个文档的标准化因子,标准化因子用来以后乘以这个这个域的命中数。

Norms (.nrm) --> SegSize

每个字节记录一个浮点数。位0-2包含了3位的尾数部分,位3-8包含了5位的指数部分。

按如下规则可将这些字节转换为IEEE标准单精度浮点数:

1. 如果该字节是0,就是浮点0;

2. 否则,设置新浮点数的标志位为0;

3. 将字节中的指数加上48后作为新的浮点数的指数;

4. 将字节中的尾数映射到新浮点数尾数的高3位;并且

5. 设置新浮点数尾数的低21位为0。

被删除的文档(Deleted document.

.del文件是可选的,只有在某段中存在删除操作后才存在:

Deletions (.del) --> ByteCount,BitCount,Bits

ByteSize,BitCount --> Uint32

Bits --> ByteCount

ByteCount表示的是Bits列表中Byte的数量。典型的,它等于(SegSize/8)+1。

BitCount表示Bits列表中多少个已经被设置过了。

Bits列表包含了一些位(bit),顺序表示一个文档。当对应于文档号的位被设置了,就标志着这个文档已经被删除了。位的顺序是从低到高。因此,如果Bits包含两个字节,0x00和0x02,那么表示文档9已经删除了。局限性(Limitations)

在以上的文件格式中,好几处都有限制项和文档的最大个数为32位数的极限,即接近于40亿。今天看来,这不会造成问题,但是,长远的看,可能造成问题。因此,这些极限应该或者换为UInt64类型的值,或者更好的,换为VInt类型的值(VInt值没有上限)。

有两处地方的代码要求必须是定长的值,他们是:

1. FieldvaluesPosition变量(存储于域索引文件中,.fdx文件)。它已经是一个UInt64型,所以不会有问题。

2. TermCount变量(存储于项信息文件中,.tis文件)。这是最后输出到文件中的,但是最先被读取,因此是存储于文件的最前端。索引代码先在这里写入一个0值,然后在其他文件输出完毕后覆盖这个值。所以无论它存储在什么地方,它都必须是一个定长的值,它应该被变成UInt64型。

除此之外,所有的UInt值都可以换成VInt型以去掉限制。

4、深入Lucene 索引机制

架构概览

图一显示了Lucene 的索引机制的架构。Lucene 使用各种解析器对各种不同类型的文档进行解析。比如对于HTML 文档,HTML 解析器会做一些预处理的工作,比如过滤文档中的HTML 标签等等。HTML 解析器的输出的是文本内容,接着Lucene 的分词器(Analyzer)从文本内容中提取出索引项以及相关信息,比如索引项的出现频率。接着Lucene 的分词器把这些信息写到索引文件中。

图一:Lucene 索引机制架构

用Lucene索引文档

接下来我将一步一步的来演示如何利用Lucene 为你的文档创建索引。只要你能将要索引的文件转化成文本格式,Lucene 就能为你的文档建立索引。比如,如果你想为HTML 文档或者PDF 文档建立索引,那么首先你就需要从这些文档中提取出文本信息,然后把文本信息交给Lucene 建立索引。我们接下来的例子用来演示如何利用Lucene 为后缀名为txt 的文件建立索引。

1.准备文本文件

首先把一些以txt 为后缀名的文本文件放到一个目录中,比如在Windows 平台上,

相关主题
相关文档 最新文档