Lucene从入门到熟悉(一)概念&建立索引

来源:互联网 发布:先锋乒羽淘宝商城微店 编辑:程序博客网 时间:2024/06/06 18:15

Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一个开源项目。也是目前最为流行的基于 Java 开源全文检索工具包。

优点:

高效-信息检索 (Information Retrieval)
成熟-经过多个项目检验
免费-开源(open-source project in Java)

Lucene经典应用

Eclipse-Eclipse的帮助系统的搜索功能。

Jive-一个广受欢迎的开放的源码的论坛项目,其目标是建设一个开放结构的、强壮的、易于扩展的基于JSP的论坛。Jive的搜索使用了lucene搜索引擎。
Cocoon-基于XML的web发布框架,全文检索部分使用了Lucene。

Lucene 索引创建读取架构图



系统架构

.核心索引类

IndexWriter :建立索引的核心组件。使用 IndexWriter 可以新建一个索引并将对象文件逐一添加到索引当中,但不可以执行读取和搜索操作。
Directory :代表一个 lucene 索引项的位置。这是一个抽象类,其具体实现有 FSDirectory和 RAMDirectory。前者将索引写入硬盘,对应于真实的文件系统路径,后者则将索引写入内存,相比于前者效率高但可用空间小。
Analyzer :对文本内容进行分析的抽象类,具体实现中有停用词切除、词干分析、大小写切换等功能。
Document :可以视作文本经过处理后所对应的对象,由多个字段组成,如路径、标题、摘要、修改日期等等。
IndexSearcher :检索操作的核心组件,用于对 IndexWriter 创建的索引执行,只读的检索操作,工作模式为接收 Query 对象而返回 Hits 对象。
Term :检索的基本单元,标示检索的字段名称和检索对象的值,如Term( “title”, “lucene” )。即表示在 title 字段中搜寻关键词 lucene 。
Query :表示查询的抽象类,由相应的 Term 来标识。
TermQuery :最基本的查询类型,用于匹配含有指定值字段的文档。
Hits :用来装载搜索结果文档队列指针的容器。

Lucene 与数据库对比

 

数据库

Lucene

概念

列/字段

Filed

行/记录

Doucument

查询(SELECT)

Searcher

操作

添加(INSERT)

IndexWriter.addDocument

删除(DELETE)

IndexWriter.delete

修改(UPDATE)

不支持(可删除后重新添加)











.内部实现简析

1)设有两篇文章1和2

文章1的内容为:Tom lives in Guangzhou,I live in Guangzhou too.
文章2的内容为:He once lived in Shanghai.

2)由于lucene是基于关键词索引和查询的,首先我们要取得这两篇文章的关键词,通常我们需要如下处理措施:
a.我们现在有的是文章内容,即一个字符串,我们先要找出字符串中的所有单词,即分词。英文单词由于用空格分隔,比较好处理。中文单词间是连在一起的需要特殊的分词处理。
b.文章中的”in”, “once” “too”等词没有什么实际意义,中文中的   “的” “是”等字通常也无具体含义,这些不代表概念的词可以过滤掉
c.用户通常希望查“He”时能把含“he”,“HE”的文章也找出来,所以所有单词需要统一大小写。
d.用户通常希望查“live”时能把含“lives”,“lived”的文章也找出来,所以需要把“lives”,“lived”还原成“live”
e.文章中的标点符号通常不表示某种概念,也可以过滤掉

3)在lucene中由Analyzer类完成经过上面处后

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

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

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

5).通常仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现次数和出现的位置,通常有两种位置:

a)字符位置,即记录该词是文章中第几个字符(优点是关键词亮显时定位快)

 b)关键词位置,即记录该词是文章中第几个关键词(优点是节约索引空间、词组(phase)查询快),lucene中记录的就是这种位置

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

关键词 文章号[出现频率] 出现位置

guangzhou1[2] 3,6

he 2[1] 1

i 1[1] 4

live 1[2],2[1] 2,5,2

shanghai 2[1] 3

tom 1[1] 1

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


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

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

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

为什么要建立索引

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

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

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



全文检索 ≠ like "%keyword%"

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


采用反向索引机制

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

非常不确定的问题

  由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题,这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制,并提供了扩展接口,以方便针对不同应用的定制。   

Lucene索引建立

大部分的搜索(数据库)引擎都是用B树结构来维护索引,索引的更新会导致大量的IO操作,Lucene在实现中,对此稍微有所改进:不是维护一个索引文件,而是在扩展索引的时候不断创建新的索引文件,然后定期的把这些新的小索引文件合并到原先的大索引中(针对不同的更新策略,批次的大小可以调整),这样在不影响检索的效率的前提下,提高了索引的效率。



                                      Lucene 索引合并过程

IndexWriter :建立索引的核心组件。

Directory:代表一个 lucene 索引项的位置。

Analyzer :对文本内容进行分析的抽象类,具体实现中有停用词切除、词干分析、大小写切换等功能。

Document :可以视作文本经过处理后所对应的对象,由多个字段组成,如路径、标题、摘要、修改日期等等。

Field :字段,对应于文本的某一部分数据,便于检索时根据结果提取。早期版本分为四个类型: Keyword 、 UnIndexed 、 UnStored 和 Text ,其主要区别归结于三个方面:是否被分析,是否被索引,是否存储于索引中。但是在最新版本的Lucene中,使用了一种更为统一的形式,也即只有Field一个类,然后使用一些参数来描述这个字段的属性,通过参数组合,可以组合出各种类别,甚至那四种不存在的类别理论上也是可以组合出来。

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

   public Field(Stringname, String value, Store store, Index index)

Lucene 底层打分机制

 lucene的score其实是 tf  * idf  * Boost * lengthNorm  计算得来


 tf : 查询的词在文档中出现的次数的平方根

idf:反转文档频率

boots:激励因子,可通过setBoots方法设置,通过filed 和 document都可以设置,所设置的值会同时起作用

lengthNorm: 由搜索的filed的长度觉得,越长文档的分值越低


控制score就是设置 boots的值

lucene会把计算后,最大分值超过1.0的分值作为分母,其他文档的分值都除以这个最大值,计算出最终的得分。

例子:

package com.firstproject.testindex;import java.io.IOException;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.analysis.standard.StandardAnalyzer;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.index.IndexReader;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.index.Term;import org.apache.lucene.store.Directory;import org.apache.lucene.store.FSDirectory;public class UpdateDocument {public static void main(String[] args) throws IOException {Analyzer analyzer=new StandardAnalyzer();String indexDir="d:/luceneindex";Directory dir=FSDirectory.getDirectory(indexDir);IndexReader reader=IndexReader.open(dir);System.out.println("before delete : "+reader.numDocs());reader.deleteDocuments(new Term("id","2"));System.out.println("after delete : "+reader.numDocs());reader.close();IndexWriter writer=new IndexWriter(dir,analyzer,true,IndexWriter.MaxFieldLength.LIMITED);Document document=new Document();Field field1=new Field("id","2",Field.Store.YES,Field.Index.ANALYZED);field1.setBoost(1.5f);document.add(field1);document.add(new Field("name","Tom",Field.Store.YES,Field.Index.NO));document.add(new Field("address","tianjin",Field.Store.YES,Field.Index.ANALYZED));//document.setBoost(1.5f);//默认1.0,大于1.0,比较重要document.setBoost(0.5f);//不重要writer.addDocument(document);writer.close();reader=IndexReader.open(dir);System.out.println("after add : "+reader.numDocs());reader.close();dir.close();}}


Lucen索引效率设置

IndexWriter Method

Default Value

Description

setMaxBufferedDocs

16M

Determines the amout of RAM that

May be used for buffering added documents before they are flushed as a new segment

setMergeFactor

10

Controls segment merge frequency and size

setMaxMergeDocs

Integer MAX_VALUE

Limit the number of documents per segment



建立索引样例代码:

package com.lucene.test.T01;import java.io.IOException;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.analysis.standard.StandardAnalyzer;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.store.Directory;import org.apache.lucene.store.FSDirectory;public class TestIndex {/** * @param args * @throws IOException */public static void main(String[] args) throws IOException {String[] ids = { "1", "2", "3", "4" };String[] names = { "zhangsan", "lisi", "wangwu", "zhaoliu" };//String[] names = { "zhangsan", "zhangsun", "zhangson", "zhaoliu" };String[] address = { "shanghai", "beijing", "guangzhou", "beijing" };String[] birthday = { "19880101", "19860105", "19760205", "19550719" };Analyzer analyzer = new StandardAnalyzer();String indexDir = "d:/temp/luceneindex";Directory dir = FSDirectory.getDirectory(indexDir);// true 表示创建或覆盖当前索引;false表示对当前索引进行追加// Default value is 128IndexWriter writer = new IndexWriter(dir, analyzer, true,IndexWriter.MaxFieldLength.LIMITED);for (int i = 0; i < ids.length; i++) {Document document = new Document();document.add(new Field("id", ids[i], Field.Store.YES,Field.Index.ANALYZED));document.add(new Field("name", names[i], Field.Store.YES,Field.Index.ANALYZED)); // Field.Index.NO表示不建立索引document.add(new Field("address", address[i], Field.Store.YES,Field.Index.NO));document.add(new Field("birthday", birthday[i], Field.Store.YES,Field.Index.ANALYZED));writer.addDocument(document);}writer.optimize();writer.close();}}


通过Document对象将数据读取,再通过IndexWriter建立索引

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 舌头有异味怎么办是有口臭吗 快8个月羊水破了怎么办 25岁欠了50万债怎么办 28岁血压高150低压110怎么办 苹果6的4g网络慢怎么办 一个月染了6次头怎么办 五0二干在衣服上怎么办 刚怀孕见红了肚子不痛怎么办 我有外遇了老婆不离婚怎么办 套了牙套的牙疼怎么办 我鼻子上有很多螨虫和黑头怎么办 鱼刺卡在喉咙怎么办最有效的办法 脚被蚊子咬了很痒怎么办 好压7z密码忘了怎么办 4g卡显示2g网络怎么办 过塑机把纸吞了怎么办 红米1s开不了机怎么办 跟老婆吵架闹的要离婚该怎么办 充了q币没有到账怎么办 9个月宝宝吃了盐怎么办 红米4x开不了机怎么办 鱼身上有红斑像出血了怎么办 草鱼身上有红斑像出血了怎么办 宝宝屁眼红的破皮了怎么办 孩子身上起红疙瘩很痒怎么办 久而不射,但软了怎么办 盆底综合肌力1级怎么办 头发掉的厉害怎么办吃什么好 给蜂蛰了肿了痒怎么办 小米手环2没电了怎么办 小米手环2不亮了怎么办 红米3s无限重启怎么办 乐视手机1s卡顿怎么办 老公出轨了怎么办你会选择离婚吗 c盘和d盘换换了怎么办 晚上2点到3点醒怎么办 红米3s变砖了怎么办 6s锁屏密码忘了怎么办 怀孕9个月了胃疼怎么办 怀孕6个月了胃疼怎么办 孕妇胃疼怎么办4个月了