Lucene学习笔记(一)

来源:互联网 发布:ubuntu 卸载自带jdk 编辑:程序博客网 时间:2024/05/04 18:53

Lucene介绍

什么是Lucene?

Lucene是Apache组织的一个子项目,它是一个高效的开源的基于java的全文检索引擎工具包。

Lucene的目的是为软件开发人员提供一个简单易用的工具包,用来方便的在系统中实现全文检索的功能,或者是以此为基础简历完整的全文检索引擎

啥是全文检索?

首先从我们生活中的数据说起,我们生活中的数据总体分为两种:结构化数据和非结构化数据。

结构化数据:指的是具有固定的格式或者有限长度的数据,例如:数据库

非结构化数据:指的是长度不固定或者格式不固定的数据,如邮件,word文档等
非结构化数据也叫全文数据

按照数据分类,搜索也分为两种
对结构化数据的搜索:比如sql查询数据库
对非结构化数据的搜索:windows的搜索文件内容

对非结构化数据也叫全文数据的搜索,主要有两种方法:顺序扫描法和反向索引法

顺序扫描法:就是顺序的扫描每个文档内容,看看是否有要搜索的关键字,实现查找文档的功能,也就是根据文档找词
反向索引法:就是提前将搜索的关键字建成索引,然后再根据索引查找文档,也就是根据词找文档

这种先建立索引,再对索引进行搜索的过程就叫做全文检索。

Lucene能做啥?

搜索引擎:例如百度谷歌
站内搜索:例如京东天猫
系统搜索:windows文件搜索

而我们使用Lucene主要场景就是开发站内搜索,但是为什么使用Lucene?sql不可以么?
sql流程


这是我们原始sql的查询方式,如果用户群比较小的话,数据库的数据量比较小,那么这种方式实现搜索功能在企业中是比较常见的。
但是一旦数据量过多时,数据库的压力就会变得很大,查询速度会变得非常慢,我们需要使用更好的解决方案,那就是Lucene


为了解决数据库压力和速度的问题,我们的数据库就变成了索引库,我们使用Lucene的API来操作服务器上索引库,这样和数据库完全分离,而且查询都是根据索引的,所以效率很快。

Lucene怎么做?


全文索引大体分为两个过程,索引创建和搜索引擎
索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程
搜索索引:就是得到用户查询请求,搜索创建的索引,然后返回结果的过程


Lucene全文检索流程分析

流程图


绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:
确定原始内容(要搜索的内容)---》获得文档---》创建文档---》分析文档---》索引文档

红色表示搜索的过程,从索引库中搜索内容,搜索过程包括:
用户通过搜索界面---》创建查询---》执行搜索,从索引库搜索---》渲染搜索结果

原始内容

指的是索引和搜索的内容
原始内容包括互联网上的网页、数据库中的数据、磁盘上的文件等。

获得文档(采集数据)

从互联网、数据库等获取需要搜索的原始信息,这个过程就是信息采集。
采集数据的目的是为了将原始内容存储到Document对象中。

如何采集

1、对于互联网上网页,可以使用工具将网页抓取到本地生成html文件。
2、数据库中的数据,可以直接连接数据库读取表中的数据。
3、文件系统中的某个文件,可以通过I/O操作读取文件的内容。

在Internet上采集信息的软件通常称为爬虫或蜘蛛,也称为网络机器人,爬虫访问互联网上的每一个网页,将获取到的网页内容存储起来。

Lucene不提供信息采集的类库,需要自己编写一个爬虫程序实现信息采集,也可以通过一些开源软件实现信息采集,如下:

Solr(http://lucene.apache.org/solr) ,solr是apache的一个子项目,支持从关系数据库、xml文档中提取原始数据。
Nutch(http://lucene.apache.org/nutch), Nutch是apache的一个子项目,包括大规模爬虫工具,能够抓取和分辨web网站数据。
jsoup(http://jsoup.org/ ),jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
heritrix(http://sourceforge.net/projects/archive-crawler/files/),Heritrix 是一个由 java 开发的、开源的网络爬虫,用户可以使用它来从网上抓取想要的资源。其最出色之处在于它良好的可扩展性,方便用户实现自己的抓取逻辑。

创建文档

创建文档的目的是统一数据格式,方便文档分析

一个Document文档中包括多个域(Field),域中存储内容,我们可以把数据库中的一条记录当成一个Document,一列当成一个Field

分析文档

分析文档主要是对Field域进行分析,分析文档的目的是为了索引。

分析文档主要通过分词组件(Tokenizer) 和语言处理组件(Linguistic Processor)
分词组件工作流程:
1.将Field域中的内容进行分词
2.去除标点符号
3.去除停用词*
经过分词之后得到的结果称为词元

所谓的停用词就是一种语言中最普通的一些单词,由于没有特别的意义,因而大多数情况下不能成为搜索的关键词,因而创建索引时,这种词会被去掉从而减少索引的大小
英文中的停用词:比如 the a is this 等等
Document1的Field域:
Students should be allowed to go out with their friends, but not allowed to drink beer.

Document2的Field域:
My friend Jerry went to school to see his students but found them drunk which is not allowed.

在我们的例子中,便得到以下词元(Token):
“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”。
将得到的词元传给语言处理组件(Linguistic Processor),语言处理组件一般做以下几点:
1.变为小写
2.将单词缩减为词根形式,例如:cars---》car 
3.将单词转变为词根形式 ,例如:drove---》drive
语言处理组件的结果称为词(Term)
Term是索引库的最小单位
在我们的例子中,经过语言处理,得到的词(Term)如下
“student”,“allow”,“go”,“their”,“friend”,“allow”,“drink”,“beer”,“my”,“friend”,“jerry”,“go”,“school”,“see”,“his”,“student”,“find”,“them”,“drink”,“allow”。
也正是因为有语言处理的步骤,才能使搜索drove,drive也能被搜出来
索引的目的是为了搜索

就是将得到的Term传给索引组件,索引组件做以下几件事:
1.利用得到的词(Term)创建一个字典,主要是赋予DocumentID
2.对字典按字母顺序排序


3.合并相同的Term,并将DocumentID成为倒排链表


DocumentFrequency 即文档频次,表示总共有多少文件包含此词(Term)
Frequency 即词频率,表示此文件中包含了几个此词(Term)
到此为止,索引创建完毕
最终的索引结构是一种倒排索引结构,也叫反向索引结构。
反向索引结构是根据内容找文档


搜索流程

搜索就是用户输入关键字,从索引中通过Term找文档的过程,根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容。

创建查询

创建查询指的就是创建查询语句,Lucene也有自己的查询语法。
最基本的有 AND OR NOT 等
举个栗子:luncene AND learned NOT hadoop
说明想找一个包含luncene和learn 但是不包括hadoop的文档

执行搜索

第一步:将创建查询语句进行词法分析,语法分析,语言处理
词法分析:上面栗子,经过词法分析,得到的单词有lucene,learned,hadoop 关键字有 AND NOT
注意:关键字必须大写,否则作为普通单词处理


语法分析
如果发现查询语句不满足语法规则,则会报错,如:lucene NOT AND learned 
语言处理
如learned变成learn等,然后搜索索引,得到文档。
1.首先在反向索引表中,分别找出包含luncene,learn,hadoop的文档链表。

2.其次,对包含luncene,learn的链表进行合并操作,得到既包含luncene又包含learn的文档链表

3.然后将此链表与hadoop的文档链表进行差操作,去除包含hadoop的文档,从而得到想要的结果

4.根据得到的文档和查询语句的相关性进行排序
渲染结果
以一个友好的界面将查询结果展示给用户

用户根据搜索结果找自己想要的信息,为了帮助用户很快找到自己的结果,提供了很多展示的效果,比如搜索结果中将关键字高亮显示,百度提供的快照等。

Lucene Java程序入门

索引流程


IndexWriter是lucene实现索引过程的核心组件,通过IndexWriter可以创建新索引 更新索引 删除索引操作,IndexWriter需要通过Directory对索引进行存储操作
Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储,它是一个抽象类,它的子类包括FSDirectory(在文件系统存储索引,)RAMDirectory

数据采集

在电商网站中,全文检索的数据源在数据库中,我们这里访问数据库。这里省略掉了book的pojo以及dao层实现

实现索引流程

1.采集数据
2.创建Document文档对象
3.创建分析器(分词器)
4.创建IndexWriterConfig配置信息类
5.创建Directory流对象,声明索引库存储位置
6.创建IndexWriter
7.把Document写入到索引库中
8.释放资源
public class CreateIndexTest {@Testpublic void Test() throws Exception{// 1. 采集数据BookDao bookDao = new BookDaoImpl();List<Book> bookList = bookDao.queryBookList();// 2. 创建Document文档对象List<Document> documents = new ArrayList<>();for (Book book : bookList) {Document document = new Document();// Document文档中添加Field域// 图书Id// Store.YES:表示存储到文档域中document.add(new TextField("id", book.getId().toString(), Store.YES));// 图书名称document.add(new TextField("name", book.getName().toString(), Store.YES));// 图书价格document.add(new TextField("price", book.getPrice().toString(), Store.YES));// 图书图片地址document.add(new TextField("pic", book.getPic().toString(), Store.YES));// 图书描述document.add(new TextField("description", book.getDescription().toString(), Store.YES));// 把Document放到list中documents.add(document);}//3.创建Analyzer分词器,分析文档,对文档进行分词Analyzer analyzer =new StandardAnalyzer();// 4. 创建Directory对象,声明索引库的位置Directory dictory =FSDirectory.open(new File("E:\\img\\"));// 5. 创建IndexWriteConfig对象,写入索引需要的配置IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_4_10_3,  analyzer);//6.创建IndexWriterIndexWriter indexWriter = new IndexWriter(dictory,indexWriterConfig);//7.写入到索引库,通过IndexWriter添加文档对象for (Document document : documents) {indexWriter.addDocument(document);}//8.释放资源indexWriter.close();}

在文件夹中出现了一下文件,表示创建索引成功

使用luke查看索引

Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过界面来进行索引文件的查询、修改




查询流程


Lucene可以通过query对象输入查询语句,同数据库的sql一样,Lucene也有固定的查询语法
举例:
找 description中包含java关键字和Lucene关键字的文档
它对应的查询语句应该是:description:java AND description:lucene

Luke软件中怎么查?

4


代码实现

1.创建Query搜索对象
2.创建Directory流对象,声明索引库位置
3.创建索引读取对象IndexReader
4.创建索引搜索对象IndexSearcher
5.使用索引搜索对象,执行搜索,返回结果集TopDocs
6.解析结果集
7.释放资源
*查询和建立索引库所使用的分词器必须保持一致,因为内部的分词方法不同。

IndexSearcher搜索方法如下:

方法

说明

indexSearcher.search(query, n)

根据Query搜索,返回评分最高的n条记录

indexSearcher.search(query, filter, n)

根据Query搜索,添加过滤策略,返回评分最高的n条记录

indexSearcher.search(query, n, sort)

根据Query搜索,添加排序策略,返回评分最高的n条记录

indexSearcher.search(booleanQuery, filter, n, sort)

根据Query搜索,添加过滤策略,添加排序策略,返回评分最高的n条记录

public class SearchIndexTest {@Testpublic void testSearchIndex() throws Exception {//1.创建query搜索对象  //创建分词器Analyzer analyzer =new StandardAnalyzer();// 创建搜索解析器,第一个参数:默认Field域,第二个参数:分词器QueryParser queryParser = new QueryParser("description",analyzer);//创建搜索对象Query query = queryParser.parse("java AND lucene");//创建Directory流对象,声明索引库位置Directory dictory =FSDirectory.open(new File("E:\\img\\"));//创建索引读取对象IndexReaderDirectoryReader reader = DirectoryReader.open(dictory);//创建索引搜索对象IndexSearcher searcher = new IndexSearcher(reader);//使用索引搜索对象,执行搜索,返回结果集TopDocs// 第一个参数:搜索对象,第二个参数:返回的数据条数,指定查询结果最顶部的n条数据返回TopDocs topDocs = searcher.search(query, 10);ScoreDoc[] scoreDocs = topDocs.scoreDocs;for (ScoreDoc scoreDoc : scoreDocs) {int doc = scoreDoc.doc;Document document = searcher.doc(doc);System.out.println("=============================");System.out.println("docID:" + doc);System.out.println("bookId:" + document.get("id"));System.out.println("name:" + document.get("name"));System.out.println("price:" + document.get("price"));System.out.println("pic:" + document.get("pic"));}reader.close();}/** * 大致流程是,首先声明创建索引时的分词器,保证分词一致性,然后通过QueryParser输入查询语句,在通过Directory对象 * 打开索引库,创建索引搜索对象,搜索description为java和lucene的,返回ids[] 遍历集合,取出每个id, * 在通过search对象.doc拿到每一个document,然后通过document.get获得里面的属性 */}

分词器

Analyzer(分析分词)使用时机
1.创建索引时,需要经过Analyzer进行分词处理,当Field的属性是tokenized(是否分词)为true时才会分词
2.搜索时,1.保证与分词时的分词器一致,使用分词器对关键字分析,分词,分析主要是把关键字和搜索词分开
比如搜索关键字:springweb ,经过分析器进行分词,得出:spring  web拿词去索引词典表查找 ,找到索引链接到Document,解析Document内容。

中文分词器

学过英文的都知道,英文是以单词为单位的,单词与单词之间以空格或者逗号句号隔开。所以对于英文,我们可以简单以空格判断某个字符串是否为一个单词,比如I love China,love 和 China很容易被程序区分开来。

而中文则以字为单位,字又组成词,字和词再组成句子。中文“我爱中国”就不一样了,电脑不知道“中国”是一个词语还是“爱中”是一个词语。

把中文的句子切分成有意义的词,就是中文分词,也称切词。我爱中国,分词的结果是:我、爱、中国。

Lucene自带中文分词器

 StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
效果:“我”、“爱”、“中”、“国”。

 CJKAnalyzer
二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。

上边两个分词器无法满足需求。
 SmartChineseAnalyzer
对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理

第三方中文分词器

 paoding: 庖丁解牛最新版在 https://code.google.com/p/paoding/ 中最多支持Lucene 3.0,且最新提交的代码在 2008-06-03,在svn中最新也是2010年提交,已经过时,不予考虑。

mmseg4j:最新版已从 https://code.google.com/p/mmseg4j/ 移至 https://github.com/chenlb/mmseg4j-solr,支持Lucene 4.10,且在github中最新提交代码是2014年6月,从09年~14年一共有:18个版本,也就是一年几乎有3个大小版本,有较大的活跃度,用了mmseg算法。

IK-analyzer: 最新版在https://code.google.com/p/ik-analyzer/上,支持Lucene 4.10从2006年12月推出1.0版开始, IKAnalyzer已经推出了4个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。从3.0版本开 始,IK发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。在2012版本中,IK实现了简单的分词 歧义排除算法,标志着IK分词器从单纯的词典分词向模拟语义分词衍化。 但是也就是2012年12月后没有在更新。

 ansj_seg:最新版本在 https://github.com/NLPchina/ansj_seg tags仅有1.1版本,从2012年到2014年更新了大小6次,但是作者本人在2014年10月10日说明:“可能我以后没有精力来维护ansj_seg了”,现在由”nlp_china”管理。2014年11月有更新。并未说明是否支持Lucene,是一个由CRF(条件随机场)算法所做的分词算法。

imdict-chinese-analyzer:最新版在 https://code.google.com/p/imdict-chinese-analyzer/ , 最新更新也在2009年5月,下载源码,不支持Lucene 4.10 。是利用HMM(隐马尔科夫链)算法。

Jcseg:最新版本在git.oschina.net/lionsoul/jcseg,支持Lucene 4.10,作者有较高的活跃度。利用mmseg算法。

我们就使用IKAnalyzer,因为它可以扩展词库

只要把上面生成和查询的分词器代码换成IKAnalyzer


如果需要扩展词库,需要加上配置文件:


ext.dic:扩展分词
stopword.dic 扩展停词

注意! 不可以使用记事本打开.dic

那样的话,格式中是含有bom的。

IKAnalyzer.cfg.xml配置文件

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  <properties>  <comment>IK Analyzer 扩展配置</comment><!--用户可以在这里配置自己的扩展字典 --><entry key="ext_dict">ext.dic;</entry> <!--用户可以在这里配置自己的扩展停止词字典--><entry key="ext_stopwords">stopword.dic;</entry> </properties>

ok,只需要在.dic文件中,添加你的分词和停词。

比如 我需要 南槿 分成一个整体词时,我就可以把南槿添加到ext.dic中,很帅添加到stop中,那么搜索南槿好帅,

只会显示南槿的结果。

------------------------------一些问题------------------------------

什么是lucene?
Lucene是一个高效的、开源的,基于Java的全文检索引擎工具包。

引擎:指的是最核心的组件
工具包:jar包。

什么是全文检索?

既然是检索,那么检索的数据是什么?

数据分类:结构化数据、非结构化数据。

结构化数据:结构固定、长度固定的数据,比如数据库数据、excel

非结构化数据:结构不固定、长度不固定的数据,比如word文档数据、互联网数据。


结构化数据搜索很简单,非结构化数据搜索比较麻烦。


重点处理的就是非结构化数据?两种方式:顺序扫描法、反向索引法。

顺序扫描法:文件越多,效率越慢,不建议使用。

反向索引法(推荐使用):指的就是先将目标文档中进行分词索引。搜索的时候,根据索引查找目标文档。


这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

全文检索的过程,就是写字典、查字典的过程。


lucene能干什么?
主要我们是使用它开发站内搜索。

为什么使用lucene开发站内搜索?使用SQL不行吗?
SQL可以实现站内搜索,但是搜索的SQL语句中,需要使用NAME LIKE'%iphone%',需要使用前后模糊查询。一旦使用前后模糊查询,那么会进行全表扫描查询。

为什么lucene可以呢?lucene内部使用反向索引法,对目标的NAME列,进行分词索引。再根据索引,查找记录。 




索引库里面究竟存些什么?(Index)
索引库从逻辑上分为索引域和文档域。

索引域里面存储什么?
Term词典:
1、一个Field对应一个Term词典。如何表示某个词典中的词呢?  域名:词,比如name:lucene和description:lucene这是不同域的词。
2、Term词典还记录了频率,分别是文档频率和词频率,也就是df和tf
文档倒排链表:
1、一个Term对应一个文档倒排链表
2、文档倒排链表,不是真实的文档链表,只是文档ID链表。
3、如果需要文档的话,根据文档链表中的文档ID去文档域查找对应的文档对象。

文档域里面存储什么?
存储的是Document对象。
Document对象中存储的是Field域。
Field中存储的是内容。


如何创建索引的?
1、创建Document对象,为什么创建Document对象?
采集到的数据格式多种多样,而lucene只认识一种数据格式,就是Document,它是二维结构。

2、分析Document对象,为什么分析Document对象?
a)分析Document其实分析是该Document中的Field
b)将Field中的内容,经过分析之后,会得到比较精简的Term
c)分析文档的主要目的,最终是形成索引(索引里面最小的单位就是Term)。

分析文档,主要包含两大部分,分别是分词组件和语言处理组件
分词组件:分词、过滤(标点符号过滤和停用词过滤)

语言处理组件:大小转小写、单词的复数、单词的原型处理


3、索引文档,将索引经过处理之后,保存到索引库中。
形成term词典
形成文档倒排链表

最终将term词典和文档倒排链表存储到索引域
将Document对象存储到文档域。