lucene和solr笔记01

========


自己的理解:
在lucene中,把格式化后的索引库中的每一条索引叫做document;
每一个term就是经过分词组件(分词、停词、标点符号过滤),和语言处理组件(大写转小写、复数转单数、时态转换等)处理过的格式化field;
因此,term和document是多对一关系,在进行索引的增删改操作时,要先获取document对象
然后使用索引写入对象IndexWriter操作

field类型
这里写图片描述

第一天

(重点:通过对Lucene的学习理解搜索相关的概念和基本原理)

  1. 什么是全文检索,如何实现全文检索

  2. Lucene实现全文检索的流程

a) 创建索引

b) 查询索引

  1. 配置开发环境

  2. 创建索引库

  3. 查询索引库

  4. 分析器的分析过程

a) 测试分析器的分词效果

b) 第三方中文分析器

c) 分词器使用时机

第二天

(重点:了解Lucene内部的数据类型有哪些特点,能清楚的知道Lucene的查询条件特性

掌握Solr的安装部署以及Solrj的代码)

  1. Lucene的Field域

  2. Lucene的索引库维护

  3. Lucene的查询

    a. Query子对象

    b. QueryParser

  4. Lucene相关度排序

  5. Solr介绍

  6. Solr安装配置与简单使用

  7. Solrj的使用(用Solrj维护和查询索引)

第三天

(重点:掌握Solr的配置方法,案例的代码)

  1. Solr的使用

a) schma.xml文件的配置

b) 配置中文分析器

c) 配置业务域

d) DataimportHandler插件

  1. Solr与Solrj的复杂查询

  2. 京东站内搜索案例

为什么学习检索技术

系统中的信息查询和读取占据了功能的绝大部分比重,采用更快更高效的查询方法会使得系统的整体性能和用户体验得到非常大的提升,因此我们很有必要进一步的学习一些更高级的查询技术。这就是我们学习这门课程的原因。

信息检索

信息检索是计算机世界中非常重要的一种功能。信息检索不仅仅是指从数据库检索数据,还包括从文件、网页、邮件、用户手输入的内容中检索数据。通过怎样的高效方式将用户想要的信息快速提取出来,是计算机技术人员研究的重点方向之一。

数据分类

从数据查询的角度将,我们的数据总体分为两种:结构化数据和非结构化数据。

结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。

非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等磁盘上的文件

结构化数据搜索方法

数据库就是最常见的结构化数据。通过SQL可以非常方便的查询数据。

为什么数据库中的数据能非常方便的搜索出来?

因为数据库中的数据存储在表中,表有行有列有类型有长度,因此才可以通过非常方便的SQL查询结果。也就是说结构化的数据有规律,所以才好进行查找。

非结构化数据查询方法

非结构化数据我们怎么查找信息?

我们考虑一个小时候学查字典的场景:小时候我们都使用过新华字典,老师叫你翻开第268页从268页到269页,找到“坑爹”的,此时你会怎么查找?——毫无疑问,你的眼睛会从268页的第一个字开始从头至尾地扫描,直到找到“坑”字为止。这种按照内容的顺序一个一个字符的查找方法叫做

顺序扫描法(Serial Scanning)。

对于少量的数据,使用顺序扫描是够用的,它的优点就是可靠,缺点就是很慢,非常慢。

{width=”3.0898337707786525in” height=”5.1in”}
{width=”3.138888888888889in”
height=”5.102487970253718in”}

但是如果老师不告诉你你坑爹的“坑”字在哪一页呢?也没有教你如何查字典呢?

你只能从第一页的第一个字逐个的扫描下去,那样你真的是被坑了。查找的过程会相当的慢,甚至会让你崩溃,所以这种坑爹的事情我们不能去做。我们要重新思考此时的查询办法。

思考一下新华字典是怎么解决汉字的快速查找的?

字典中把有利于快速查找的内容从正文内容中整理出来,形成了一张结构化的表,我们利用这个表就能够快速的定位汉字在正文中的位置。

新华字典中整理出来的结构化表包括:汉语拼音音节索引、部首检字表、四角号码检字表。

新华字典中非结构化的部分:字典正文。

{width=”2.855403543307087in”
height=”1.3958333333333333in”}

下图是汉语拼音音节索引:

{width=”3.0615846456692912in”
height=”4.326388888888889in”}

这部分从非结构化数据中提取出来,重新组织成结构化的信息,我们称之索引

**这种检索索引来定位内容在正文中位置的查询方式就叫全文检索(Full-text
Search)。**

使用全文检索的前提:对原始数据建立索引

实现技术

Apache提供了一个开源的全文检索开发包——Lucene。它提供了完整的查询模块和索引模块,利用这些核心模块,开发人员可以开发出全文检索应用软件。同时Apache也提供了基于Lucene开发的成型的搜索产品——Solr。

实际项目中直接使用Solr,而不会用Lucene。因为Lucene太底层了,许多搜索的细节都需要程序员自己开发,所以不利于实际项目中应用,浪费时间、开发成本很大、最终开发出来的东西还不一定有Solr好用和强大,因此实际项目都会选用Solr,或者其他成型的可以直接使用的搜索软件。

我们的课程中先学习Lucene的目的:明白全文检索的索引创建的过程和搜索过程。

简单说就是先来熟悉一下概念和基本过程,这样再学习solr的时候就更加简单理解了。

{width=”5.365402449693788in”
height=”4.358490813648294in”}

注意:红框内全是索引的数据来源(不仅仅局限于数据库),一切可以采集的数据都可以被建立索引。

全文检索的应用场景

系统的站内搜索服务。还有专业的搜索引擎中也有全文检索技术的使用,比如百度、Google等,但专业的搜索引擎不只使用这一种搜索技术。

比如:京东的商品搜索就是搜索的索引而不是数据库,当你输入【电脑笔记本】时,得到的查询结果中【电脑笔记本】并不是一个整体固定的词汇,查询出的结果中有【笔记本】【笔记本电脑】相关的都查询了出来而我输入的却是【电脑笔记本】。

系统的数据查询方案

有了全文检索技术我们就可以创建一个索引库,将我们的数据查询方案进行改进:

{width=”7.268055555555556in”
height=”3.740972222222222in”}

·基本的数据查询方案在面对查询量大的应用时会对数据库造成极大的压力,而且查询效率会很低。

·改进后的数据查询方案将读写进行了分离,将查询量大、搜索数据范围广的请求应用分发给了索引库,查询索引库就有效的分担了数据库的压力。只不过此时要做好数据的同步处理。

Lucene实现全文检索的过程

创建索引和查询过程

{width=”7.268055555555556in”
height=”5.1506944444444445in”}

说明:

  1. 绿色表示创建索引过程,包括:

采集数据构建文档对象分析文档对象创建索引(保存到索引库)

  1. 红色表示查询索引过程,包括:

入口提交查询请求(查询关键字)创建查询对象执行查询(从索引库搜索)渲染结果显示查询结果

索引过程

索引过程就是将原始内容变得有结构,类似于数据库的表结构一样,这样才有利于查询。创建索引时不会改变原始文档的任何内容,只是将有用信息的拷贝重新组织成索引。

假设有如下两个原始文档:

采集数据

从互联网上、数据库、文件系统中等获取原始信息,这个过程就是信息采集,信息采集的目的是为了对原始内容进行索引。Lucene不提供信息采集的类库,需要自己编写程序,也可以通过一些开源软件实现信息采集。

如何采集数据?

1、互联网上的网页:可以使用网络爬虫技术(Nutch、jsoup、heritrix等)抓取网页数据到本地保存。

2、数据库中的数据:可以直接连接数据库用SQL查询数据。

3、文件系统中的文件:可以通过I/O操作读取文件的内容。

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
开发的、开源的网络爬虫,用户可以使用它来从网上抓取想要的资源。其最出色之处在于它良好的可扩展性,方便用户实现自己的抓取逻辑。

上面的两个文件可以通过I/O操作采集的信息归纳如下:


students.txt myfriends.txt

文件名:students.txt 文件名:myfriends.txt

文件路径:C:\mydir\03_workspace 文件路径:C:\mydir\03_workspace

文件大小:10K 文件大小:5K

文件内容:。。。。。。 文件内容:。。。。。。


构建文档对象

采集上来的数据格式各式各样,我们需要先统一格式然后才能处理,Lucene的统一格式就是Document文档对象

一个Document对象类似数据库表的一条记录,可以包含多个Field域,Field就相当于表的字段,Field域有两个属性:域名(name)和域值(value),和数据库表类似,每个文档都有一个唯一的主键——文档id(docID)。例如:


{width=”2.1226957567804026in” height=”1.3277777777777777in”} 为了便于理解可以将Document对象进一步表示成一个二维表格:

                                                                                   ![](media/image12.png){width="4.5680555555555555in" height="0.40809601924759403in"}

                                                                                   这样就和我们的数据库表的结构差不多了。

                                                                                   数据库把**行**叫做**记录**,**列**叫做**字段**,索引库把**行**叫做**文档**(Document),**列**叫做**域**(Field)

Document结构更加灵活,不限制Field域的数量、种类和是否重复,只要是Field就可以加入Document对象。

我们用来演示的两个文档采集来构建文档对象后,整理出来如下:

{width=”6.8in” height=”0.8790748031496063in”}

该阶段形成的表还不够,要想实现快速查询需要进一步处理。因为我们的目标是:通过一个查询条件关键词就可以一下知道包含这个关键词的文档记录都有哪些,而不是一个一个文档的找,那样太慢了。

分析文档对象(重点)

分析文档主要是按照Field域进行分析,分析结果是将域(Field)的内容转换成一个一个的单词,这样方便查找。这些个单词就是最基本的索引单元——词项(Term)

{width=”6.8in” height=”0.8790748031496063in”}

分析过程中文档ID是创建文档自动生成的,所以它不参与分析。

用表的结构来理解就是按列为单位进行分析,只不过这里的列叫做Field域。不同列(Field域)之间分析出来的相同结果(Term)是不可以合并的。

分析过程都经历了什么?如下:下面所经历的过程都是Lucene内部的处理过程,不需要我们编码实现。

{width=”4.8903433945756785in”
height=”3.301281714785652in”}

分词组件(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传给语言处理组件。

语言处理组件(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是创建索引的最小单元,也是搜索索引时的最小单元。

创建索引

(Lucene自动完成)

{width=”7.268055555555556in”
height=”2.9229166666666666in”}

创建字典表

利用得到的词项(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

对字典表按字母顺序排序

对字典表按字母顺序排序:

排序结果如下:

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

合并相同词项,归纳文档倒排链表

每一个Term词项都属于某一列(Field域),合并只能在同一列(Field域)内部进行,不同列(Field域)中如果有相同的单词是不能进行合并的。合并相同的词项(Term),它们对应的DocId归纳成为文档倒排(Posting
List)链表。Term与倒排链表之间建立一个外键关联。合并的同时还要统计出这个Term出现的次数。

最终我们演示的示例合并后的结果如下:

{width=”5.653543307086614in”
height=”5.244094488188976in”}

●Document Frequency(DF):文档频次,表示多少文档出现过此词(Term)

●Term Frequency(TF):词频,表示某个文档中该词(Term)出现过几次

例如:对词项(Term)
“allow”来讲,总共有两篇(DF)文档包含此Term,Term后面的文档链表总共有两个,第一个表示包含“allow”的第一篇文档,即DocId=1的文档,此文档中“allow”出现了2次(TF),第二个表示包含“allow”的第二个文档,即DocId=2的文档,此文档中,”allow”出现了1次(TF)。

索引表+倒排链表为什么可以实现更快速的查询?

有了索引表和文档倒排链表我们就可以根据一个关键词一下得到和这个词关联的文档ID有哪些了,有了ID就可以快速的得到文档记录Document对象,就可以从中取得原始数据了。无疑这样的查找方式比一个一个文档的搜索要快上N倍。就跟查字典一样。

索引流程总结

{width=”7.268055555555556in” height=”4.49375in”}

索引库由索引表 + 文档倒排链表 + 文档表共同组成

●索引表:保存索引词项的

●文档倒排链表:保存词项关联的文档ID

●文档对象表:保存文档具体内容

索引表特点:

●一个Term词项必须属于某个Field域(和数据库中的数据肯定属于某一列的道理一致。)

●索引表的形成是按照列(Field)为单位的,所以不同Field域中即使有相同的Term也不能合并,合并只在同一列(Field)内发生。

基于上述特点我们也意识到,在实际查询时必须要确定域名才行,否则无法执行查询。

查询索引

用户查询入口

(手动编程)

搜索界面用于提交用户搜索关键字,同时搜索完成展示搜索结果的功能。这部分不是Lucene的范畴,做成什么样由用户需求决定。

{width=”6.5828772965879265in” height=”4.25in”}

创建查询对象

执行查询前Lucene要先构建一个查询对象Query,Query包含Field域名和查询条件值,Query对象在执行时会解释成具体的查询语法,这些都是在Lucene内部进行,用户不需感知。

例如:语法“fileName:lucene”表示要搜索【文件名】Field域中内容包含“lucene”的文档。根据这个语法会生成一个查询对象Query,然后再执行这个查询对象。

执行查询

搜索索引过程:

就是根据查询对象Query生成的实际查询语法去索引表进行查询,然后再从关联的倒排链表中找到全部对应文档的ID,然后根据ID逐个取得文档对象,从中拿到想要的信息。这样就实现了快速查询文档。

IMG_256{width=”5.847916666666666in”
height=”1.5in”}

渲染结果

根据自己希望的样子将数据展示给用户。其中最简单的特征就是关键字高亮显示。不过这部分不属于搜索实现的范畴,是表现层的事情。

{width=”5.768055555555556in”
height=”3.5729166666666665in”}

配置开发环境

Lucene下载

Lucene是开发全文检索功能的工具包,从官方网站下载Lucene4.10.3,并解压。

官方网站:http://lucene.apache.org/

版本:lucene4.10.3

Jdk要求:1.7以上

IDE:Eclipse

使用的jar包

下载后的压缩包解压缩:

{width=”2.8854166666666665in” height=”1.3125in”}

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包

{width=”4.0625in” height=”1.1875in”}

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

{width=”4.677083333333333in”
height=”1.3541666666666667in”}

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

{width=”4.395833333333333in”
height=”1.3229166666666667in”}

其它:用于处理文件内容的工具包

commons-io-2.4.jar

创建java工程

创建一个java工程,编码格式utf-8,并导入jar包并导入Junit测试的jar。

入门程序

需求

实现一个文件的搜索功能,通过关键字搜索文件,[[]{#OLE_LINK1
.anchor}]{#OLE_LINK2
.anchor}凡是文件名或文件内容包括关键字的文件都需要找出来。还可以根据中文词语进行查询,并且需要支持多个条件查询。

本案例中的原始内容就是磁盘上的文件,如下图:

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

{width=”5.7659722222222225in”
height=”2.9743055555555555in”}

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

使用IndexWriter的对象创建索引。

创建索引

实现步骤

第一步:创建IndexWriter对象(创建索引的准备工作)

1)指定索引库的存放位置Directory对象

2)创建一个分析器,对document对象中Field域的内容进行分析,4.2节说的过程都是由这个分析器完成的。

3)创建一个IndexWriterConfig对象,用于配置创建索引所需的信息

参数1:Lucene的版本(可以选择对应的版本,也可以选择LATEST)

参数2:分析器对象

4)根据Directory对象和IndexWriterConfig对象创建IndexWriter对象

第二步:开始创建索引

1)采集原始数据

2)创建document对象

根据业务需求创建Field域对象来保存原始数据中的各部分内容

(参数1:域名、参数2:域值、参数3:是否存储)

把上面创建好的Field对象添加进document对象中。

3)用IndexWriter对象创建索引

(添加过程:用IndexWriter对象添加并分析文档对象,然后创建索引,并写入索引库)

第三步:关闭IndexWriter对象(关闭中带有提交处理)

代码实现

【CreateIndexTest.java】


  **package** cn.itcast.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】中出现了以下文件,表示创建索引成功

{width=”3.3541666666666665in”
height=”1.8310575240594926in”}

使用Luke工具查看索引文件

使用luke工具。Luke是一个便于使用Lucene开发和诊断的第三方工具,它可以访问现有利用Lucene创建的索引,并允许显示和修改。

  1. 启动工具:直接双击【start.bat】或者在控制台输入【java -jar
    lukeall-4.10.3.jar】

{width=”2.2666666666666666in”
height=”0.8149048556430446in”}

  1. 选择索引库位置

{width=”4.922155511811024in”
height=”3.933333333333333in”}

3. 索引域的展示效果:

{width=”4.907801837270341in”
height=”3.977777777777778in”}

4. 文档域的展示效果:

{width=”4.888391294838145in” height=”4.0375in”}

查询索引

实现步骤

第一步:查询准备工作(创建IndexReader、IndexSearcher对象)

1)指定索引库的存放位置Directory对象

2)根据Directory对象创建IndexReader对象

3)根据IndexReader对象创建IndexSearcher对象

第二步:创建查询条件对象(创建一个Term的精确查询——方式一)

第三步:执行查询(参数1:查询条件对象,参数2:查询结果返回的最大值)

第四步:处理查询结果

1)输出结果数量

2)遍历查询结果并输出

第五步:关闭IndexReader对象

代码实现


// 查询索引

@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();

  }

TopDocs

Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:

方法或属性 说明


totalHits 匹配搜索条件的总记录数
scoreDocs 顶部匹配的文档ID的记录

注意:

Search方法需要指定匹配记录数量n:indexSearcher.search(query, n)

TopDocs.totalHits:是匹配索引库中所有记录的数量

TopDocs.scoreDocs:相关度排名靠前的记录数组,scoreDocs的长度小于等于search方法指定的参数n

分析器

分析器是索引的关键,如果分词分不好,创建出来的索引根本没有任何意义没法使用。所以我们来认识一下分析器。

分析器(Analyzer)组成

{width=”4.8903433945756785in”
height=”3.301281714785652in”}

分析器是分析文档对象处理环节的核心对象,其中包括两方面:分词组件和语言处理组件

分词组件实际是由若干个小组件组成的:分词器、标点过滤器、停用词过滤器。。。

语言处理组件也一样,根据语言的不同会由不同的语言还原组件组成。

因此不同语言,不同用途的分析器中,包含的小组件是不同的,这些小组件串联起来共同完成文档对象的分析工作

中文分析器

对于分词来说,不同的语言,分词规则是不同的,比如英语每个单词都是用空格分隔,所以拆分词的规则比较简单,我们可以简单以空格判断某个字符串是否为一个单词,比如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

{width=”4.362923228346457in”
height=”2.7777777777777777in”}

使用方法:

第一步:把jar包添加到工程中

第二步:把配置文件和扩展词典和停用词词典添加到classpath下

注意:mydict.dic和ext_stopword.dic文件的格式为UTF-8,注意是无[[[[]{#OLE_LINK97
.anchor}]{#OLE_LINK96 .anchor}]{#OLE_LINK94 .anchor}]{#OLE_LINK95
.anchor}BOM 的UTF-8 编码。

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

{width=”5.768055555555556in”
height=”2.0597222222222222in”}

添加jar包

在【资料\jar\IK】下找到IKAnalyzer的jar包

{width=”3.2708333333333335in”
height=”0.3333333333333333in”}

修改代码

IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法一样,将创建索引的测试代码中的【StandardAnalyzer】改为【IKAnalyzer】测试中文分词效果。

可以和之前使用StandardAnalyzer分析器创建的索引可以对比一下:

StandardAnalyzer分析得出的索引结果:{width=”4.429133858267717in”
height=”3.5826771653543306in”}

IKAnalyzer分析得出的索引结果:

{width=”4.455838801399825in”
height=”3.611111111111111in”}

从结果看出IKAnalyzer能更好的从语义上识别中文,并做出比较正确的切分词。

扩展词库的使用

IKAnalyzer允许用户扩展自己的中文词库,包括扩展词库和停用词库。

扩展词库:是把一些特殊的专有名词加进来,这样分词的时候就会把专有名词当成一个整体,不会被切分。

停用词库:是把一些想过滤掉的词加进来,这样分词后就会被过滤器过滤掉,不作为索引的语汇单元。

扩展词库文件与停用词库文件

下载下来的IK压缩包中可能有停用词库,但没有扩展词库,但可以手动创建,但要注意:在创建词库时,不要用windows自带的记事本保存词库文件,因为windows默认格式是含有bom文件头的,这是个不可见文件标识符号,IK识别的时候会出错,因为非windows系统都是不带bom文件头的。

{width=”3.059055118110236in”
height=”1.594488188976378in”}

扩展词库【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


自带的没有【的】【地】【得】,给它加进去。

配置词库

【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下:

{width=”1.8391896325459318in”
height=”1.9236111111111112in”}

测试

为了便于测试结果的确认,在数据库book表中把每一条记录的description中都加入:【《计算机科学丛书:Java编程思想(第4版)》【传智播客】】这一段话,这样可以增加【编程思想】和【传智播客】的出现频率,搜索排名会靠前。

  1. 不加扩展词库和停用词库时创建索引的结果:

停用词没有被过滤掉:and,的,the等都被加进了索引库

扩展词【编程思想】【传值播客】被分开了

{width=”4.4679910323709535in”
height=”3.6319444444444446in”}

{width=”4.552002405949256in”
height=”3.6666666666666665in”}

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

{width=”3.2152777777777777in”
height=”1.0515857392825896in”}

如果加入log4j,再次运行的log:

{width=”2.486111111111111in”
height=”0.2612795275590551in”}

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

{width=”4.6979494750656166in”
height=”3.7916666666666665in”}

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

{width=”3.5555555555555554in”
height=”1.1934514435695538in”}

再次运行的log:

{width=”2.6287882764654418in”
height=”0.4720428696412948in”}

已经看到扩展词没有被切分:

【传值播客】是纯粹的专有名词,所以完全的被保留,没有切分

【编程思想】并不是纯粹的专有名词,在IK的内部的中文分词器中仍然会识别【编程】和【思想】,然后你又追加了【编程思想】,所以最终是三个词【编程】【思想】【编程思想】

{width=”4.85742782152231in”
height=”3.9166666666666665in”}

分析器Analyzer使用时机

索引中使用Analyzer的时机

创建索引时分析器有使用的时候也有不需要分析器的时候。大部分文档的Field内容是需要被分析的,但也有一些特殊的Field域的内容是不用分析,可以直接作为Term创建索引。例如:

1、不作为查询条件的内容,比如文件路径

2、不是匹配内容中的词而匹配Field域整体的项目,比如订单号、身份证号等。

{width=”5.768055555555556in”
height=”3.3368055555555554in”}

搜索时的Analyzer

用户输入的查询内容也是需要进行分析的,这个过程和创建索引时的分析是一样的,因此他们必须使用一致的分析器对象,否则会出现双方分析出来的Term对应不上,这样就无法进行查询了。

注意:搜索使用的分析器要和索引使用的分析器一致。

和索引时一样,查询是也存在一些特殊的查询是不需要分析的,比如根据订单号、身份证号查询等。

猜你喜欢

转载自blog.csdn.net/qq_32332777/article/details/79058127
今日推荐