搜索
您的当前位置:首页正文

Lucene

来源:小奈知识网
Lucene 学习笔记

第一章Lucene概述

认识Lucene:

 Lucene 的贡献者Doug Cutting是一位资深全文索引/检索专家,曾经是V-Twin搜索

引擎(Apple的Copland操作系统的成就之一)的主要开发者,后在Excite担任高级系统架构设计师,目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。2001年年底成为APACHE基金会jakarta的一个子项目。

 Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,

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

Lucene的特点:

     

灵活的接口函数

分块索引和批量索引 数据源灵活多样 索引字段可以定制 索引文件与平台无关 面向对象的系统架构

第二章Lucene系统框架

系统架构

核心包及其功能说明

核心包名 功能说明 org.apache.lucene.analysis org.apache.lucene.document org.apache.lucene.index 语言分析器;主要用于分词 索引存储的文档管理,类似关系数据系统中的表 索引管理:增加、修改、删除索引等 org.apache.lucene.queryParser 查询分析器:实现查询关键词的运算 org.apache.lucene.search org.apache.lucene.store org.apache.lucene.util 检索管理:根据查询条件,检索到结果 数据存储管理:主要包括底层的一些I/O操作 包括一些公共的使用类 Lucene 的核心索引类

 IndexWriter :建立索引的核心组件。使用 IndexWriter 可以新建一个索引并将对象

文件逐一添加到索引当中,但不可以执行读取和搜索操作。

 Directory :代表一个 lucene 索引项的位置。这是一个抽象类,其具体实现有

FSDirectory和 RAMDirectory。前者将索引写入硬盘,对应于真实的文件系统路径,后者则将索引写入内存,相比于前者效率高但可用空间小。

 Analyzer :对文本内容进行分析的抽象类,具体实现中有停用词切除、词干分析、

大小写切换等功能。

 Document :可以视作文本经过处理后所对应的对象,由多个字段组成,如路径、

标题、摘要、修改日期等等。

 Field :字段,对应于文本的某一部分数据,便于检索时根据结果提取。 Store Index NO YES 存储,但是不建立索引,当然也就不分析。这样的字段无法搜索,但是会出现在搜索结果中。 不分析,但是索引存储。 (高级) NO COMPRESS 无意义。引发illegal 基本等同于YES,但是外Argument 加了压缩。对于较长的文Exception。 本和二进制的字段,应该选用这个参数。计算量更大。 ANALYZED NOT_ANALYZED 分析,索引,存储 分析,索引,不存储 分析,索引,压缩存储。 不分析,直接索引但是不存储。 (高级) (高级) 不分析,但是索引压缩存储。 (高级) (高级) NOT_ANALYZED_NO_NORMS (高级) ANALYZED_NO_NORMS package com.firstproject.testfield; import org.apache.lucene.document.Field; public class Test01 { } //定义一个Field Field field =new Field(\"fname\",\"fvalue\",Field.Store.YES,Field.Index.ANALYZED);

Lucene 的核心检索类

 IndexSearcher :检索操作的核心组件,用于对 IndexWriter 创建的索引执行,只读

   

的检索操作,工作模式为接收 Query 对象而返回 Hits 对象。

Term :检索的基本单元,标示检索的字段名称和检索对象的值,如Term( “title”, “lucene” )。即表示在 title 字段中搜寻关键词 lucene 。 Query :表示查询的抽象类,由相应的 Term 来标识。

TermQuery :最基本的查询类型,用于匹配含有指定值字段的文档。 Hits :用来装载搜索结果文档队列指针的容器。

Lucene与数据库的类比

数据库 Lucene 概念 列/字段 行/记录 Field Document 查询(SELECT) Searcher 操 作 添加(INSERT) 删除(DELETE) IndexWriter. addDocument IndexReader.delete 修改(UPDATE) 不支持(可删除后重新添加)

Lucene内部实现简析

设有两篇文章

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

由于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]

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

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

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

加上“出现频率”和“出现位置”信息后,我们的索引结构变为: 关键词 文章号[出现频率] 出现位置 guangzhou 1[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将上面三列分别作为词典文件(Term Dictionary)、频率文件(frequencies)、位置文件 (positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位

置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。

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

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

假设要查询单词 “live”,lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。 而用普通的顺序匹配算法,不建索引,对所有文章的内容进行字符串匹配,这个过程将会相当缓慢,当文章数目很大时,时间往往是无法忍受的。 例1: 导入jar包 TestLucene.java package com.firstproject.test; import java.io.IOException; import java.io.StringReader; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Token; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.standard.StandardAnalyzer; public class TestLucene { public static void main(String[] args) throws IOException { //1.使用的是标准分词器 Analyzer analyzer=new StandardAnalyzer(); //2.生成Token流 TokenStream tokenStream=analyzer.tokenStream(\"\new StringReader(\"this is first lucene project!\")); //3.创建Token Token token =new Token(); while(tokenStream.next(token)!=null){ System.out.println(token.term()); } } } 运行结果: first lucene project 备注: This ,is 是停用词是过滤掉的

第三章索引

为什么要索引

 高效-通常比较厚的书籍后面常常附关键词索引表(比如:北京:12, 34页, 上海:

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

 全文检索 ≠ like \"%keyword%\" 由于数据库索引不是为全文索引设计的,因此,使

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

 采用反向索引机制 所以建立一个高效检索系统的关键是建立一个类似于科技索引

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

 非常不确定的问题 由此可以看出模糊查询相对数据库的精确查询是一个非常不确

定的问题,这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制,并提供了扩展接口,以方便针对不同应用的定制。

Lucene索引的建立

图1 索引建立的过程

Lucene索引的创新之处

 大部分的搜索(数据库)引擎都是用B树结构来维护索引,索引的更新会导致大量

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

图2 索引合并的过程

Lucene 的核心索引类

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

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

 Analyzer :对文本内容进行分析的抽象类,具体实现中有停用词切除、词干分析、

大小写切换等功能。

 Document :可以视作文本经过处理后所对应的对象,由多个字段组成,如路径、

标题、摘要、修改日期等等。

 Field :字段,对应于文本的某一部分数据,便于检索时根据结果提取。早期版本分

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

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

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

因篇幅问题不能全部显示,请点此查看更多更全内容

Top