Lucene&Solr框架之第一篇

2.信息检索
信息检索是计算机世界中很是重要的一种功能。信息检索不只仅是指从数据库检索数据,还包括从文件、网页、邮件、用户手输入的内容中检索数据。经过怎样的高效方式将用户想要的信息快速提取出来,是计算机技术人员研究的重点方向之一。
2.1.数据分类
咱们生活中的数据整体分为两种:结构化数据和非结构化数据。
结构化数据:指具备固定格式或有限长度的数据,如数据库,元数据等。
非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等磁盘上的文件
2.2.结构化数据搜索方法
数据库就是最多见的结构化数据。经过SQL能够很是方便的查询数据。
为何数据库中的数据能很是方便的搜索出来?
由于数据库中的数据存储在表中,表有行有列有类型有长度,所以才能够经过很是方便的SQL查询结果。也就是说结构化的数据有规律,因此才好进行查找。
试想一下若是数据没有进行结构化,没有任何规律该如何查询?
2.3.非结构化数据查询方法
咱们考虑一个小时候学查字典的场景:小时候咱们都使用过新华字典,老师叫你翻开第268页从268页到269页,找到“坑爹”的坑,此时你会怎么查找?——毫无疑问,你的眼睛会从268页的第一个字开始从头到尾地扫描,直到找到“坑”字为止。这种按照内容的顺序一个一个字符的查找方法叫作顺序扫描法(Serial Scanning)。对于少许的数据,使用顺序扫描是够用的。html

 

可是若是老师不告诉你你坑爹的“坑”字在哪一页呢?也没有教你如何查字典呢?
你只能从第一页的第一个字逐个的扫描下去,那样你真的是被坑了。查找的过程会至关的慢,甚至会让你崩溃,因此这种坑爹的事情咱们不能去作。咱们要从新思考此时的查询办法。java

思考一下新华字典是怎么解决汉字的快速查找的?
从一堆没有结构的内容中提取出来文字的位置信息(页码)、文字写法(文字自己)、汉语拼音,而后将它们从新整理、排序、概括,最终造成一张结构化的表,咱们叫作汉语拼音音节索引表。
汉语拼音索引中记录了“坑”字在哪一页的信息,只要你知道“坑”字的汉语拼音,就能够快速的查找到“坑”字在哪,这样答案就出来了。
下图是汉语拼音音节索引表:
git

重新华字典的例子总结一下,如何从一堆没有规律没有结构的信息中快速的查找咱们须要的信息?
最有效的方法就是先将信息从新组织(提取、整理、排序、概括),造成新的集合(即一个更方便更高效查找的集合),而后查询这个结构化的集合,从中找出你要找的信息在原文中的位置。
简单概括成:
非结构化结构化保存结构化
查找结构化获得在非结构化中的定位github

这部分从非结构化数据中提取出来,从新组织的结构化信息,咱们称之索引。
这种先对全文创建索引集合,再对索引集合进行检索的查询方式就叫全文检索(Full-text Search)。
建立索引的过程会不会很繁琐费时?
是的,很繁琐费时间,可是值得的,由于索引一旦建立就能够屡次使用,最终能够带来高效的查询速度,是一件一劳永逸的事情。
2.4.如何实现全文检索
Apache提供了一个开源的全文检索开发框架——Lucene。它提供了完整的查询模块和索引模块,利用这些核心模块,开发人员能够方便、快速的开发出全文检索应用。
根据上面的简单概括能够知道咱们须要使用Lucene要作两件事情:
非结构化结构化保存结构化————建立索引
查找结构化获得在非结构化中是定位————查询索引web

注意:索引的数据来源不只仅局限于数据库,一切能够采集的数据均可以被创建索引。
2.5.系统的数据查询方案算法

·基本的数据查询方案在面对查询量大的应用时会对数据库形成极大的压力,并且查询效率会很低。
·改进后的数据查询方案将读写进行了分离,将查询量大的应用的查询请求分发给了索引库,查询直接走索引库,不走数据库,这样就有效的下降了数据库的压力,同时索引库查询的高效特性也可以保证查询效率。
2.6.全文检索的应用场景
全文检索应用最多的就是开发站内搜索服务。尤为是对于电商系统,大数据量的搜索都是使用的站内搜索服务。
还有专业的搜索引擎中也有全文检索技术的使用,好比百度、Google等,但专业的搜索引擎不仅使用这一种搜索技术。
3.Lucene实现全文检索的流程
3.1.建立索引和查询索引流程数据库

说明:
1.绿色表示建立索引过程,包括:
采集数据构建文档对象分析文档对象建立索引(保存到索引库)apache

2.红色表示查询索引过程,包括:
入口提交查询请求(查询关键字)建立查询对象执行查询(从索引库搜索)渲染结果显示查询结果
3.2.索引流程
用户将想要搜索的原始数据建立索引,索引内容存储在索引库(index)中。建立索引时不会改变原始文档的任何内容,只是将有用信息的拷贝从新组织成索引。
假设有以下两个原始文档:

【students.txt】:Students should be allowed to go out with their friends, but not allowed to drink beer.
【myfriends.txt】:My friend Jerry went to school to see his students but found them drunk which is not allowed.编程

3.2.1.采集数据windows

(手动编程)
从互联网上、数据库、文件系统中等获取须要搜索的原始信息,这个过程就是信息采集,信息采集的目的是为了对原始内容进行索引。
如何采集数据?
一、互联网上的网页:可使用工具将网页抓取到本地生成html文件。
二、数据库中的数据:能够直接链接数据库用SQL查询数据。
三、文件系统中的文件:能够经过I/O操做读取文件的内容。

在Internet上采集信息的软件一般称为爬虫或蜘蛛,也称为网络机器人,爬虫访问互联网上的每个网页,将获取到的网页内容存储起来。
Lucene不提供信息采集的类库,须要本身编写一个爬虫程序实现信息采集,也能够经过一些开源软件实现信息采集,以下:
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 开发的、开源的网络爬虫,用户可使用它来从网上抓取想要的资源。其最出色之处在于它良好的可扩展性,方便用户实现本身的抓取逻辑。
3.2.2.构建文档对象
(手动new)
采集上来的数据格式各式各样,咱们须要先统一格式而后才能处理,Lucene中的统一格式就是Document文档对象。
一个Document对象相似数据库表的一条记录,能够包含多个Field域,Field域有两个属性:域名(name)和域值(value),Field就至关于表的字段。
Document结构更加灵活,不限制Field域的数量、种类和是否重复,只要是Field就能够加入Document对象。
和数据库表相似,每一个文档都有一个惟一的主键——文档id(docID)。



上图是将磁盘上的一个文件采集出来的数据放入一个Document对象。Document对象中包括四个Field(file_name、file_path、file_size、file_content)
3.2.3.分析文档对象(重点)
(Lucene自动完成)
分析文档主要是对文档的Field域进行分析,目的是为了建立索引作好准备。分析的过程是将域(Field)的内容转换成最基本的索引单元——项(Term)的过程。

3.2.3.1.分词组件(Tokenizer)
分词组件(Tokenizer)会作如下几件事情(这个过程称为:Tokenize):
1. 分词器将Field域内容分红一个一个单独的单词
2. 标点符号过滤器去除内容中的标点符号
3. 停用词过滤器去除停用词(stop word)
什么是停用词?所谓停词(Stop word)就是一种语言中没有具体含义的词,于是大多数状况下不会做为搜索的关键词,这样一来建立索引时能减小索引的大小。英语中停词(Stop word)如:”the”、”a”、”this”,中文有:”的,得”等。不一样语种的分词组件(Tokenizer),都有本身的停词(stop word)集合。

通过分词(Tokenize)以后获得的结果称为词汇单元(Token)。上面的两个文档的content域内容通过分析后获得如下词汇单元:
“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”
将Token传给语言处理组件。
3.2.3.2.语言处理组件(Linguistic Processor)
语言处理组件(linguistic processor)主要是对获得的词元(Token)作一些语言相关的处理。对于英语,语言处理组件(Linguistic Processor)通常作如下几点:
1. 变为小写(Lowercase)
2. 复数变单数(stemming) 如”cars”到”car”
3. 词形还原(lemmatization) ,如”drove”到”drive”
通过语言处理组件(linguistic processor)处理以后获得的结果称为词项(Term),它是建立索引的最小单元。上面的Token通过处理后获得的词项(Term)以下:
"student","allow","go","their","friend","allow","drink","beer","my","friend","jerry","go","school","see","his","student","find","them","drink","allow"。
通过语言处理后,搜索drive时原文中是drove的也能被搜索出来。对文档中的各个Field域进行逐个分析,最终造成了许多的Term词项。

综上所述,分析文档的最终产物是Term,Term是建立索引的最小单元,也是搜索索引时的最小单元。
3.2.4.建立索引
(Lucene自动完成)

3.2.4.1.建立字典表
利用获得的词项(Term)建立一个字典表,一列是Term词项,一列是文档ID(DocId)
字典表以下:
Term DocId
student 1
allow 1
go 1
their 1
friend 1
allow 1
drink 1
beer 1
my 2
friend 2
jerry 2
go 2
school 2
see 2
his 2
student 2
find 2
them 2
drink 2
allow 2

3.2.4.2.对字典表按字母顺序排序
对字典表按字母顺序排序:
排序结果以下:
Term DocId
allow 1
allow 1
allow 2
beer 1
drink 1
drink 2
find 2
friend 1
friend 2
go 1
go 2
his 2
jerry 2
my 2
school 2
see 2
student 1
student 2
their 1
them 2

3.2.4.3.合并相同词项,概括文档倒排链表
建立好的Term词项实际是包含两部分信息:一是Term出自哪一个域,二是Term的内容。合并相同的词项(Term)成为文档倒排(Posting List)链表。
●合并规则:
●在比较Term是否相同时,不考虑是否在同一个Document对象中,合并时暂时忽略它。
●不一样的域(Field)中拆分出来的相同的单词是不一样的Term,不能合并。
例如:文件名中包含apache和文件内容中包含的apache是不一样的Term。
●同名域(Field)的相同单词是相同的Term,能够合并。
例如:两个文档中都有【文件名】Field域中都含有Java,这两个Java就是一个Term(域和单词都相同)

例子是以两个文档的【content】域做为演示的例子,所以只要单词相同就是相同的Term,就能够合并。合并结果以下:合并的同时要记录这个Term来自于哪一个文档以及出现的次数。

●Document Frequency(DF):文档频次,表示多少文档出现过此词(Term)
●Frequency(TF):词频,表示某个文档中该词(Term)出现过几回
例如:对词项(Term) “allow”来说,总共有两篇(DF)文档包含此Term,Term后面的文档链表总共有两个,第一个表示包含“allow”的第一篇文档,即DocId=1的文档,此文档中“allow”出现了2次(TF),第二个表示包含“allow”的第二个文档,即DocId=2的文档,此文档中,”allow”出现了1次(TF)。
索引表 + 文档倒排链表 + 文档对象集合, 共同组成了索引库
●索引表是保存索引词项的
●文档倒排链表是保存包含词项的文档ID的
●文档对象集合是保存文档具体内容的
3.2.5.索引流程总结


3.3.查询索引
查询索引就是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容。
3.3.1.用户
用户能够是天然人,也能够是远程调用的程序。
3.3.2.用户搜索界面
(手动编程)
搜索界面用于提交用户搜索关键字的,也至关于采集数据的做用。
好比:

注意:Lucene不提供制做用户搜索界面的功能,须要根据本身的需求开发搜索界面。
3.3.3.分析用户搜索关键字
(手动调用由Lucene自带的或第三方提供的解析器完成)
此处的分析过程跟索引流程中的分析文档对象的过程必需要一致。通过分析最终造成词项Term,只不过这个Term还缺乏是属于那个Field域的部分。
分析过程对于索引流程和查询索引流程要一致的缘由是,若是不一致会形成两边最终的分词结果不一样,这样会什么也搜索不到的。
3.3.4.建立查询对象
(手动new或手动调用解析器生成)
给上面的Term指定Field域,在实际应用的时候,用户查询时是没有要指定Field域的地方,那咱们该如何搜索呢?全部的搜索服务都存在一个默认域,默认域是将多个已知Field合并并优化的Field,因此查询这个默认Field域的效率会更高。
好比上面的淘宝的站内搜索,假设须要对商品名称Field域和商品描述Field域进行关键字的查询,就能够将这两个Field合并成一个新的Field域,并将这个新的Field域指定成默认域,具体的合并过程Lucene中不进行学习,咱们会展Solr中学习使用。
例如:默认域名为product_keywords,那么会在Lucene内部造成一个查询对象Query,在Query对象内部会生成查询语句:“product_keywords:台灯”。
建立查询对象在明天会详细讲解。
3.3.5.执行查询
(Lucene自动完成)
好比,在淘宝页面上查询台灯关键字,选择过滤条件:光源类型为LED,开关类型为调光开关之外,建立查询对象后实际生成的查询语句是:
product_keywords:台灯 AND product_keywords:LED NOT product_keywords:调光开关
Lucene的查询语句和SQL查询条件相似,有关键字有条件。若是在程序中调用Lucene全文检索服务时,能够在程序中直接写相似上面的查询语句的,就好咱们在JDBC程序中写SQL是同样的做用。

对条件进行解析并执行查询:(三步)
●第一步:对查询语句进行词法分析、语法分析及语言处理
1. 词法分析
如上述例子中,通过词法分析,获得单词有台灯,LED,调光开关, 关键字有AND, NOT。
注意:关键字必须大写,不然就做为普通单词处理。关键字有AND、OR、NOT。

2. 语法分析
若是发现查询语句不知足语法规则,则会报错。如product_keywords:台灯 NOT AND 调光开关,则会出错。
若是查询语句知足语法规则,就会造成语法树以下:

3. 语言处理
如LED变成led等。
通过第三步,咱们获得一棵通过语言处理的语法树。

●第二步:搜索索引,获得符合语法树的文档
1. 首先,在反向索引表中,分别找出包含lucene,learn,hadoop的文档链表。


2. 其次,对包含lucene,learn的链表进行合并操做,获得既包含lucene又包含learn的文档链表。


3. 而后,将此链表与hadoop的文档链表进行差操做,去除包含hadoop的文档,从而获得既包含lucene又包含learn并且不包含hadoop的文档链表。


4. 此文档链表就是咱们要找的文档。


●第三步:根据获得的文档和查询语句的相关性,对结果进行排序
(Lucene自动计算排序,明天会讲相关性排序)

3.3.6.渲染结果
以一个友好的界面将查询结果展现给用户,用户根据搜索结果找本身想要的信息,为了帮助用户很快找到本身的结果,提供了不少展现的效果,好比搜索结果中将关键字高亮显示,百度提供的快照等。

 


3.4.总结
综上,采集来的原始数据通过分析处理造成了索引库,经过查询条件查询索引表能够获得相关的Term词项,由此从该Term关联的文档倒排链表中获得在Document对象集合中的定位信息(DocId),而后经过DocId就能够从Document集合中获得相关的Document对象,最终能够从Document对象的指定Field域中取值返回给用户。
4.配置开发环境
4.1.Lucene下载
Lucene是开发全文检索功能的工具包,从官方网站下载Lucene4.10.3,并解压。
官方网站:http://lucene.apache.org/
版本:lucene4.10.3
Jdk要求:1.7以上
IDE:Eclipse
4.2.使用的jar包
下载后的压缩包解压缩:


Lucene基本开发jar包:
lucene-core-4.10.3.jar
lucene-analyzers-common-4.10.3.jar
lucene-queryparser-4.10.3.jar

1) lucene-core-4.10.3.jar的位置:这是Lucene的核心jar包


2) lucene-analyzers-common-4.10.3.jar的位置:这是Lucene的分析器的核心jar包


3) lucene-queryparser-4.10.3.jar的位置:这是Lucene的查询解析器jar包

其它:用于处理文件内容的工具包
commons-io-2.4.jar

4.3.建立java工程
建立一个java工程,编码格式utf-8,并导入jar包并导入Junit测试的jar。
5.入门程序
5.1.需求
实现一个文件的搜索功能,经过关键字搜索文件,凡是文件名或文件内容包括关键字的文件都须要找出来。还能够根据中文词语进行查询,而且须要支持多个条件查询。
本案例中的原始内容就是磁盘上的文件,以下图:

这里咱们要搜索的文档是磁盘上的文本文件,咱们要把凡是文件名或文件内容中包括关键字的文件都要找出来,因此这里要对文件名和文件内容建立索引。

 


本案例咱们要获取磁盘上文件的内容,能够经过文件流来读取文本文件的内容,对于pdf、doc、xls等文件可经过第三方提供的解析工具读取文件内容,好比Apache POI读取doc和xls的文件内容。

使用IndexWriter的对象建立索引。
5.2.建立索引
5.2.1.实现步骤
第一步:建立IndexWriter对象(建立索引的准备工做)
1)指定索引库的存放位置Directory对象
2)建立一个分析器,对document对象中Field域的内容进行分析
3)建立一个IndexWriterConfig对象,用于配置建立索引所需的信息
参数1:Lucene的版本(能够选择对应的版本,也能够选择LATEST)
参数2:分析器对象
4)根据Directory对象和IndexWriterConfig对象建立IndexWriter对象
第二步:开始建立索引
1)采集原始数据
2)建立document对象
根据业务需求建立Field域对象来保存原始数据中的各部份内容
(参数1:域名、参数2:域值、参数3:是否存储)
把上面建立好的Field对象添加进document对象中。
3)用IndexWriter对象建立索引
(添加过程:用IndexWriter对象添加并分析文档对象,而后建立索引,并写入索引库)
第三步:关闭IndexWriter对象(关闭中带有提交处理)
5.2.2.代码实现
【CreateIndexTest.java】
package cn.baidu.test;
import java.io.File;
import org.apache.commons.io.FileUtils;
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.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

public class CreateIndexTest {
/**
* 建立IndexWriter(建立索引准备工做)
*/
private IndexWriter createIndexWriter(String indexRepositoryPath) throws Exception {
// 建立Directory对象
Directory dir = FSDirectory.open(new File(indexRepositoryPath));
// 索引库还能够存放到内存中
// Directory directory = new RAMDirectory();
// 建立一个标准分析器
Analyzer analyzer = new StandardAnalyzer();
// 建立IndexWriterConfig对象
// 参数1: Lucene的版本信息, 能够选择对应的Lucene版本也可使用LATEST
// 参数2: 分析器对象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
// 建立IndexWriter对象
return new IndexWriter(dir, config);
}

/**
* 建立索引
*/
@Test
public void testCreateIndex() throws Exception {
// 第一步:建立IndexWriter(建立索引的准备工做)
IndexWriter indexWriter = createIndexWriter("C:\\mydir\\03_workspace\\lucene\\index");
// 第二步:开始建立索引
// 采集原始数据(从指定的目录下取得文件对象列表集合)
File dirSource = new File("C:\\mydir\\03_workspace\\searchsource");
// 遍历文件对象列表
for (File f : dirSource.listFiles()) {
// 文件名
String fileName = f.getName();
// 文件内容
String fileContent = FileUtils.readFileToString(f, "utf-8");
// 文件路径
String filePath = f.getPath();
// 文件大小
long fileSize = FileUtils.sizeOf(f);
// 建立文件名域: 参数1:域的名称, 参数2:域的内容, 参数3:是否存储
TextField fileNameField = new TextField("filename", fileName, Store.YES);
// 建立文件内容域
TextField fileContentField = new TextField("content", fileContent, Store.YES);
// 建立文件路径域
TextField filePathField = new TextField("path", filePath, Store.YES);
// 建立文件大小域
TextField fileSizeField = new TextField("size", String.valueOf(fileSize), Store.YES);

// 建立document对象
Document document = new Document();
document.add(fileNameField);
document.add(fileContentField);
document.add(filePathField);
document.add(fileSizeField);

// 建立索引(用indexWriter对象)
indexWriter.addDocument(document);
}
// 第三步:关闭indexWriter
indexWriter.close();
}
}
执行效果:
在文件夹【C:\mydir\03_workspace\lucene\index】中出现了如下文件,表示建立索引成功

5.2.3.使用Luke工具查看索引文件
使用luke工具。Luke是一个便于使用Lucene开发和诊断的第三方工具,它能够访问现有利用Lucene建立的索引,并容许显示和修改。
1.启动工具:直接双击【start.bat】或者在控制台输入【java -jar lukeall-4.10.3.jar】

2.选择索引库位置

3. 索引域的展现效果:

4. 文档域的展现效果:

5.3.查询索引
5.3.1.实现步骤
第一步:查询准备工做(建立IndexReader、IndexSearcher对象)
1)指定索引库的存放位置Directory对象
2)根据Directory对象建立IndexReader对象
3)根据IndexReader对象建立IndexSearcher对象
第二步:建立查询条件对象(建立一个Term的精确查询——方式一)
第三步:执行查询(参数1:查询条件对象,参数2:查询结果返回的最大值)
第四步:处理查询结果
1)输出结果数量
2)遍历查询结果并输出
第五步:关闭IndexReader对象
5.3.2.代码实现
// 查询索引
@Test
public void testSearchIndex() throws Exception {
// 第一步:查询准备工做
// 建立Directory对象
Directory dir = FSDirectory.open(new File("C:\\mydir\\03_workspace\\lucene\\index"));
// 建立IndexReader对象
IndexReader reader = DirectoryReader.open(dir);
// 建立IndexSearcher对象
IndexSearcher searcher = new IndexSearcher(reader);
// 第二步:建立查询条件对象
// 手动建立查询对象时是没有指定任何分析器的, 因此手动建立的查询对象没有分析查询语句的能力,
// 只能设置什么就查什么, 并且指定什么就查询什么
TermQuery query = new TermQuery(new Term("filename", "apache"));
// 第三步:执行查询(参数1:查询条件对象,参数2:查询结果返回的最大值)
TopDocs topDocs = searcher.search(query, 10);
// 第四步:处理查询结果
// 输出结果数量
System.out.println("查询的结果数量:" + topDocs.totalHits);
// 取得结果集
// 这个就是查询索引的结果,可是这个里面没有具体的内容,
// 只是关于文件名中包含apache的文件的文档ID和具体相关度的计算结果值
// 要想取得文件详细内容能够根据文档ID,利用IndexSearcher对象查询
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
// 遍历结果集
for (ScoreDoc scoreDoc : scoreDocs) {
// 根据文档对象ID取得文档对象
Document doc = searcher.doc(scoreDoc.doc);
// 打印搜索结果内容
// 文件名称
System.out.println("文件名称:" + doc.get("filename"));
// 文件路径
System.out.println("文件路径:" + doc.get("path"));
// 文件大小
System.out.println("文件大小:" + doc.get("size"));
}
// 关闭IndexReader对象
reader.close();
}

5.3.3.TopDocs
Lucene搜索结果可经过TopDocs遍历,TopDocs类提供了少许的属性,以下:

方法或属性 说明
totalHits 匹配搜索条件的总记录数
scoreDocs 顶部匹配记录
注意:
Search方法须要指定匹配记录数量n:indexSearcher.search(query, n)
TopDocs.totalHits:是匹配索引库中全部记录的数量
TopDocs.scoreDocs:相关度排名靠前的记录数组,scoreDocs的长度小于等于search方法指定的参数n
6.分析器
分析器是索引的关键,若是分词分很差,建立出来的索引根本没有任何意义无法使用。因此咱们来认识一下分析器。
6.1.分析器(Analyzer)的执行过程
一个分析器就是一个管道,其中由一个分词器对象 + 若干个过滤器对象串行组成。输入的内容通过逐层过滤最终分解成语汇单元Token,以下图是语汇单元的生成过程:

Token是分析器的直接产物。Token自己也是一个对象,它里面也包含了一些关于这个词的重要信息。
Token对象详细内容:
·词语自己内容
·在当前这段话(Field域值中保存的)中开始位置、结束位置
·一个能够存储其余杂项信息的payload对象(忽略)。
6.2.分析器的分词效果
若是想要看看分析器的分析效果,只须要看TokenStream中的内容就能够了。每一个分析器都有一个方法tokenStream,返回一个tokenStream对象:
//查看标准分析器的分词效果
@Test
public void testTokenStream() throws Exception {
//建立一个标准分析器对象
Analyzer analyzer = new StandardAnalyzer();
//得到tokenStream对象
//第一个参数:域名,能够随便给一个
//第二个参数:要分析的文本内容
TokenStream tokenStream = analyzer.tokenStream("test", "The Spring Framework provides a comprehensive programming and configuration model.");
//添加一个引用,能够得到每一个关键词
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
//添加一个偏移量的引用,记录了关键词的开始位置以及结束位置
OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
//将指针调整到列表的头部
tokenStream.reset();
//遍历关键词列表,经过incrementToken方法判断列表是否结束
while(tokenStream.incrementToken()) {
//关键词的起始位置
System.out.println("start->" + offsetAttribute.startOffset());
//取关键词
System.out.println(charTermAttribute);
//结束位置
System.out.println("end->" + offsetAttribute.endOffset());
}
tokenStream.close();
}

6.3.中文分析器
对于分词来讲,不一样的语言,分词规则是不一样的,好比英语每一个单词都是用空格分隔,因此拆分词的规则比较简单,咱们能够简单以空格判断某个字符串是否为一个单词,好比I love China,love 和 China很容易被程序区分开来。
汉字就不一样了,中文是以字为单位的,字组成词,字和词再组成句子。因此它的词必须根据语义分析后才能正确的拆分,因此拆分词的规则会很复杂。好比:“我爱中国”,电脑不知道“中国”是一个词语仍是“爱中”是一个词语。把中文的句子切分红有意义的词就是中文分词,也称切词。“我爱中国”,正确的分词结果是:我、爱、中国。
6.3.1.Lucene自带中文分析器
StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
效果:“我”、“爱”、“中”、“国”。
CJKAnalyzer
二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。

上边两个分词器没法知足需求。
SmartChineseAnalyzer
对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等很差处理
6.3.2.第三方中文分析器
 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算法。
6.4.中文分析器——IKAnalyzer


使用方法:
第一步:把jar包添加到工程中
第二步:把配置文件和扩展词典和停用词词典添加到classpath下

注意:mydict.dic和ext_stopword.dic文件的格式为UTF-8,注意是无BOM 的UTF-8 编码。

使用EditPlus.exe保存为无BOM 的UTF-8 编码格式,以下图:

6.4.1.添加jar包
在【资料\jar\IK】下找到IKAnalyzer的jar包

6.4.2.修改代码
IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法同样,将建立索引的测试代码中的【StandardAnalyzer】改成【IKAnalyzer】测试中文分词效果。
能够和以前使用StandardAnalyzer分析器建立的索引能够对比一下:
StandardAnalyzer分析得出的索引结果:

IKAnalyzer分析得出的索引结果:

从结果看出IKAnalyzer能更好的从语义上识别中文,并作出比较正确的切分词。
6.4.3.扩展词库的使用
IKAnalyzer容许用户扩展本身的中文词库,包括扩展词库和停用词库。
扩展词库:是把一些特殊的专有名词加进来,这样分词的时候就会把专有名词当成一个总体,不会被切分。
停用词库:是把一些想过滤掉的词加进来,这样分词后就会被过滤器过滤掉,不做为索引的语汇单元。
6.4.3.1.扩展词库文件与停用词库文件
下载下来的IK压缩包中可能有停用词库,但没有扩展词库,但能够手动建立,但要注意:在建立词库时,不要用windows自带的记事本保存词库文件,由于windows默认格式是含有bom文件头的,这是个不可见文件标识符号,IK识别的时候会出错,由于非windows系统都是不带bom文件头的。

扩展词库【ext.dic】
编程思想
传智播客

停用词库【stopword.dic】
a
an
and
are
as
at
be
but
by
for
if
in
into
is
it
no
not
of
on
or
such
that
the
their
then
there
these
they
this
to
was
will
with



自带的没有【的】【地】【得】,给它加进去。
6.4.3.2.配置词库
【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>
把词库文件和配置文件都放到工程的config下:

6.4.3.3.测试
为了便于测试结果的确认,在数据库book表中把每一条记录的description中都加入:【《计算机科学丛书:Java编程思想(第4版)》【传智播客】】这一段话,这样能够增长【编程思想】和【传智播客】的出现频率,搜索排名会靠前。
1.不加扩展词库和停用词库时建立索引的结果:
停用词没有被过滤掉:and,的,the等都被加进了索引库
扩展词【编程思想】【传值播客】被分开了


2.添加停用词库后从新建立索引(将原来的索引文件删除,注意:要先关闭Luke)

若是加入log4j,再次运行的log:

已经看不到被停用的单词了:


3.添加扩展词库后从新建立索引(将原来的索引文件删除,注意:要先关闭Luke)

再次运行的log:

已经看到扩展词没有被切分:
【传值播客】是纯粹的专有名词,因此彻底的被保留,没有切分
【编程思想】并非纯粹的专有名词,在IK的内部的中文分词器中仍然会识别【编程】和【思想】,而后你又追加了【编程思想】,因此最终是三个词【编程】【思想】【编程思想】

6.5.分析器Analyzer使用时机
6.5.1.索引时使用的Analyzer
建立索引时对文档对象的内容进行分析是一个必要的过程,大部分文档内容都是须要被分析的,但也有一些特殊的Field域的内容是不用分析,能够直接做为Term建立索引。
对于一些Field能够不用分析:
一、不做为查询条件的内容,好比文件路径
二、不是匹配内容中的词而匹配Field的总体内容,好比订单号、身份证号等。

6.5.2.搜索时使用Analyzer 用户输入的查询内容也须要进行分析,这个过程和建立索引时的分析是同样的,所以他们必须使用一致的分析器对象,不然会出现双方分析出来的Term对应不上,这样就没法进行查询了。 注意:搜索使用的分析器要和索引使用的分析器一致。 和索引时同样,查询是也存在一些特殊的查询是不须要分析的,好比根据订单号、身份证号查询等。

相关文章
相关标签/搜索