Lucene概述
Lucene是一个全文检索的框架,是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。所以我们先来了解一下什么是全文检索。
全文检索又叫做全文搜索,他首先是一种搜索。我们可以先思考一下,在平时使用电脑时,哪些地方用到过搜索?(这里单指普通的搜索,还不是全文检索。)
现实生活中搜索
搜索的目的是快速准确的从大段文字中快速准确的找到需要内容,我们所用到的搜索主要分为以下4种:
1. windows系统中的有搜索功能:打开“我的电脑”,按“F3”就可以使用查找的功能,查找指定的文件或文件夹。搜索的范围是整个电脑中的文件资源
2.clipse中的帮助子系统:点击HelpHelp Contents,可以查找出相关的帮助信息。搜索的范围是Eclipse的所有帮助文件,Eclipse的帮助就是用Lucene做的。
3.在CSDN、BLOG、新闻等系统中提供的搜索文章的功能,如这里的贴吧的例子。搜索的范围是系统内的文章数据(都在数据库中)。
4.搜索引擎,如Baidu或Google等,可以查询到互联网中的网页、PDF、DOC、PPT、图片、音乐、视频等。搜索范围是整个互联网的网页.
案例分析
对以上几种案例从搜索类型,搜索范围,搜索速度进行分析。
常见搜索 | 搜索类型 | 搜索范围 | 搜索速度 |
Windows资源管理器 | 文件搜索 | 电脑上面文件 | 很慢 |
Eclipse帮助文档搜索 | 全文索引搜索 | 帮助文档 | 快 |
数据库搜索 | 数据库搜索 | 数据库表 | 慢 |
互联网搜索 | 全文索引搜索 | 互联网资源 | 很快 |
从以上分析可以看出只要是使用全文索引速度都相对较快,并且还有一下共同特征,接下来来我们就来详细看一下什么是全文索引。
全文搜索是什么?
从全文数据中进行检索就叫全文检索(全文搜索)。是基于文本的搜索。
结构化数据:指具有“固定格式”或“有限长度”的数据,如数据库,元数据等;
非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等;
半结构化数据,如XML,HTML等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。
全文数据搜索方式
1.顺序扫描法(Serial Scanning)
所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。比如Window自带的搜索,在数据库中扫描不带索引文本字段等。
如何提速?
之前的XP系统中搜索都非常慢,因为他采用上面的顺序扫描法,在WIN 7中为了改善这种搜索的速度,提供了如下功能:
如何提升全文检索的速度?
2. 全文检索
非结构化数据顺序扫描很慢,对结构化数据的搜索却相对较快(由于结构化数据有一定的结构可以采取一定的搜索算法加快速度),那么把我们的非结构化数据想办法弄得有一定结构不就行了吗?关系数据库中存储的都是结构化数据,因此很检索都比较快。
![]() |
从非结构化数据中提取出的然后重新组织的信息,我们称之索引。即为文本数据建立类似字典目录,从而提高检索速度。
3. 全文检索的特点
相关度最高的排在最前面,官网中相关的网页排在最前面;对摘要进行了截取;关键词的高亮;只关注文本,不考虑语义。
比如在输入框中输入“地球上最高的山是什么”,搜索引擎不会以对话的形式告诉你“是珠穆朗玛峰”,而仅仅是列出包含了搜索关键字的网页。
在搜索引擎中查询关键词或句子时,你在最后加上问号也是没有意义的做法,会直接被过滤掉。
使用场景
1.替换数据库模糊查询,提高查询速度和准确度
1.1数据库缺点
Where title like ‘%java%’
全文检索也是一种搜索,我们之前也学过了数据库也支持搜索,如果这两种搜索用途一致,那就没必要学习全文检索了。下面就来把全文检索和数据库来做一个对比,简述下数据库搜索的弊端。
有这样一句SQL:Select * from article where content like ‘%java%’,执行以后会有如下结果: 根据我们写的SQL中使用的like可以知道,这种方式在搜索时不会索引。在数据库中复制上面的SQL,在SQL前添加explain命令,代表解释这句SQL,可以看到: 这是一个全表查询,在数据库中使用like查询的字段,会按照内容顺序,逐字扫描。需要的内容越是靠后,查询的时间久越长。 ----查询速度慢 不光包含java单词的数据会被查询出来,如果内容中有JavaScript这个单词的同样也会被查询出来。如果刚好100条结果99条都包含JavaScript,只有1条是我们真正要的结果。面对这么多无用的数据,用户肯定会非常无奈。 ----搜索效果不好
|
1.2具体说明
关系数据库中进行模糊查询时,数据库自带的全文索引将不起作用,此时需要通过全文检索来提高速度。 比如:网站系统中针对内容的模糊查询; select * from article where content like '%源代码%' ERP系统中产品等数据的模糊查询,CSDN、BLOG中的文章搜索等; |
2.全文索引是搜索引擎的基础
各种搜索引擎运行依赖于全文检索,百度谷歌的界面看着很简单,技术主要体现在后台全文检索技术的实现上,他们自己都有基于全文检索做了实现,这些技术肯定不是开源的,不然他们在整个是市场也没啥优势了。我们要学习的Lucene是开源的,目前使用最广的全文检索工具包;
3. 只对“指定领域”的网站进行索引与搜索(即垂直搜索)
如“818工作搜索”、“有道购物搜索”
比如我们过来学习,需要找房子吧。为了解决这个问题我们首先会想到什么?到58同城等网站里搜索。那找工作呢?这类网站非常多了,有中华英才网、智联招聘,51job(前程无忧),那么要在这些网站中找工作,分别得在各个网站中进行搜索,同时我们还得在每个网站中维护简历,简直太麻烦了。而垂直检索就是为了解决这类问题的
4.要在word、pdf等各种各样的数据格式中检索内容
特别是pdf,能够支持搜索的主要还是指内容是文字的。搜索到关键字以后大多会以黄底或者蓝底高亮显示。
5. 其它场合:比如搜狐拼音输入法、Google输入法等
我们常用的输入法也是使用的全文检索,输入法对词库中的词都建立了目录,我们通过拼音甚至拼音首字母都可以快速找到需要的词。
全文检索应用架构
全文检索引擎就是一个搜索引擎,要进行搜索必须有一个数据源。在①处可以看到,我们的数据源可以来自下面四个地方:
文件系统:我们的操作系统搜索是通过顺序搜索速度相当的慢,在这也可以通过全文检索来提高搜索速度;
数据库:数据库对于文本字段的内容不能建立索引,在以后应用中也会在数据库基础上集成全文检索来提高检索速度;
互联网:目前网络中的数据量已经达到百亿单位,在搜索时能够为所有的网页建立目录也是可以提高搜索速度。
其他输入:可以直接对直接输入的内容建立索引和储存。
有了数据,目的就是为了给用户提供搜索服务。在②处用户界面主要有两个功能:
获取用户输入的搜索关键字,发送请求到后台进行处理;
响应查询,展示搜索结果。
目前我们使用的搜索引擎数据源来自互联网的其他公司,自己只提供了一个非常简单的搜索界面,感觉就是一个普通的JSP,没有什么技术含量。其实不同搜索引擎的技术主要体现在③处,我们可以看到对于索引库操作主要分为两步:
对外部数据的索引建立,必要时索引库也可以储存数据;
响应用户的搜索请求,检索结果,进行相关排序后返回到前台。
![]() |
这里再分别简述下两个过程:
索引的创建:首先需要找到数据库源,即待索引的文件;然后进行词法分析,即分词,去停词,去标点符号,大写变小写等,语言处理主要是针对英文,时态的转变等;这些基础处理完成后就可以按一定顺序合并相同的词后生成倒排词链表;把需要储存的文件内容放入索引库中储存同时为文档生成一个编号;最后把倒排词链表和文档的编号建立关联就OK。
索引的搜索:在获取到用户的查询语句后,同样也需要进行词法分析,语言处理,在全文检索中匹配的最小单位是词,索引索引的建立和搜索时必须使用相同的词法分析器进行分词;语法分析主要是确定符合各个词的结果间采取怎样的取舍规则,交集或并集等;然后经过步骤(d),在索引储存库中获取到每个词所对应的结果集;在搜索索引节点处按照分析规则进行结果的筛选处理;最后把结果进行相关性排序然后返回到用户界面.
全文索引核心
1.1. 引入
任何技术都有一些核心,全文搜索也有核心,而它的核心分为:索引创建,索引搜索。接下来我们就一一的来看。
1.2. 索引创建
将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。
那么索引里面究竟存的什么,以及如何创建索引呢?在这通过下面的例子来解答这个问题。
首先构造三个不同的句子,有长有短:
在①处分别为3个句子加上编号,然后进行分词,把被一个单词分解出来与编号对应放在②处;在搜索的过程总,对于搜索的过程中大写和小写指的都是同一个单词,在这就没有区分的必要,按规则统一变为小写放在③处;要加快搜索速度,就必须保证这些单词的排列时有一定规则,这里按照字母顺序排列后放在④处;最后再简化索引,合并相同的单词,就得到如下结果:
通常在数据库中我们都是根据文档找到内容,而这里是通过词,能够快速找到包含他的文档,这就是文档倒排链表。
1.3. 索引搜索
就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。
比如我们要搜索java world两个关键词,符合java的有1,2两个文档,符合world的有1,3两个文档,在搜索引擎中直接这样排列两个词他们之间是OR的关系,出现其中一个都可以被找到,所以这里3个都会出来。全文检索中是有相关性排序的,那么结果在是怎么排列的呢?hello java world中包含两个关键字排在第一,另两个都包含一个关键字,得到结果,hello lucene world排在第二,java在最长的句子中占的权重最低排在结果集的第三。从这里可以看出相关度排序还是有一定规则的。
1.4. 总结
全文检索的核心就是索引的创建过程与搜索流程。
Lucene入门
1.1. Lucene是什么
Apache Lucene是一个用Java写的高性能、可伸缩的全文检索引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。Lucene的目标是为各种中小型应用程序加入全文检索功能。
Lucene的核心作者:Doug Cutting是一位资深全文索引/检索专家。
版本发布情况:2000年3月,最初版发布,2001年9月,加入apache;2004年7月,发布1.4正式版;2009年11月,发布2.9.1(jdk1.4)及3.0(jdk1.5)版本;2015年3月,发布4.10.4。2016年2月,发布5.5.0。
本质:一个Jar包,一个用于全文检索的框架
作用:Lucene不是一个完整的全文索引应用,而是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。而数据库执行模糊查询都需要对全表扫描或索引扫描意味着消耗大量IO,如果模糊查询经常发生,会造成数据库性能恶化。
好处 :
1 . 索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。
2 . 在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。
3 . 优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。
4 . 设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口。
5 . 已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search)、分组查询等等。
6 . 开源,可扩展能力强,有各种语言版本,适合各种平台,
场景:基于Lucene框架的搜索引擎有很多,如Solr,Elastic Search,Nutch等
中文 : 对于中文用户来说,最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍,你会了解到由于Lucene良好架构设计,对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的支持。
主要的包:
1.2. Helloworld
Lucene的索引库和数据库一样,都提供相应的API来便捷操作。
Lucene中的索引维护使用IndexWriter,由这个类提供添删改相关的操作;索引的搜索则是使用IndexSearcher进行索引的搜索。HelloWorld代码如下。
1.2.1. 创建索引
步骤:
1、 把文本内容转换为Document对象
文本是作为Document对象的一个字段而存在
2、准备IndexWriter(索引写入器)
3 、通过IndexWriter,把Document添加到缓冲区并提交
addDocument
commit
close
//创建索引的数据 现在写死,以后根据实际应用场景
String doc1 = "hello world";
String doc2 = "hello java world";
String doc3 = "hello lucene world";
privateString path ="F:/eclipse/workspace/lucene/index/
hello";
@Test
publicvoid testCreate() {
try {
//2、准备IndexWriter(索引写入器)
//索引库的位置 FS fileSystem
Directory d = FSDirectory.open(Paths.get(path ));
//分词器
Analyzer analyzer = newStandardAnalyzer();
//索引写入器的配置对象
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(d, conf);
System.out.println(indexWriter);
//1、 把文本内容转换为Document对象
//把文本转换为document对象
Document document1 = new Document();
//标题字段
document1.add(new TextField("title", "doc1", Store.YES));
document1.add(new TextField("content", doc1, Store.YES));
//添加document到缓冲区
indexWriter.addDocument(document1);
Document document2 = new Document();
//标题字段
document2.add(new TextField("title", "doc2", Store.YES));
document2.add(new TextField("content", doc2, Store.YES));
//添加document到缓冲区
indexWriter.addDocument(document2);
Document document3 = new Document();
//标题字段
document3.add(new TextField("title", "doc3", Store.YES));
document3.add(new TextField("content", doc3, Store.YES));
//3 、通过IndexWriter,把Document添加到缓冲区并提交
//添加document到缓冲区
indexWriter.addDocument(document3);
indexWriter.commit();
indexWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// OpenMode=create 每次都会重置索引库然后重新添加索引文档
// 后者覆盖前者(默认是不覆盖累加模式)
conf.setOpenMode(OpenMode.CREATE);
图形界面客户端使用
1.2.2. 搜索索引
1 封装查询提交为查询对象
2 准备IndexSearcher
3 使用IndexSearcher传入查询对象做查询-----查询出来只是文档编号DocID
4 通过IndexSearcher传入DocID获取文档
5 把文档转换为前台需要的对象 Docment----> Article
@Test
publicvoid testSearch() {
String keyWord = "lucene";
try {
// * 1 封装查询提交为查询对象
//通过查询解析器解析一个字符串为查询对象
String f = "content"; //查询的默认字段名,
Analyzer a = new StandardAnalyzer();//查询关键字要分词,所有需要分词器
QueryParser parser = new QueryParser(f, a);
Query query = parser.parse("content:"+keyWord);
// * 2 准备IndexSearcher
Directory d = FSDirectory.open(Paths.get(path ));
IndexReader r = DirectoryReader.open(d);
IndexSearcher searcher = new IndexSearcher(r);
// * 3 使用IndexSearcher传入查询对象做查询-----查询出来只是文档编号DocID
TopDocs topDocs = searcher.search(query, 1000);//查询ton条记录 前多少条记录
System.out.println("总命中数:"+topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;//命中的所有的文档的封装(docId)
// * 4 通过IndexSearcher传入DocID获取文档
for (ScoreDoc scoreDoc : scoreDocs) {
intdocId = scoreDoc.doc;
Document document = searcher.doc(docId);
// * 5 把文档转换为前台需要的对象 Docment----> Article
System.out.println("=======================================");
System.out.println("title:"+document.get("title")
+",content:"+document.get("content"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
|
|
1.3. 小结
Lucence中索引操作的核心对象是IndexWriter和IndexReader和indexSearch
2. Lucene API详解
前面已经讲了luncene的核心,但还有很多细节,也就是一些LuceneAPI使用,接下来一一讲解。
2.1. 索引目录Directory
Directory是一个对索引目录的一个抽象。索引目录用于存放lucene索引文件。直接根据一个文件夹地址来创建索引目录使用SimpleFSDirectory。
MMapDirectory : 针对64系统,它在维护索引库时,会结合“内存”与硬盘同步来处理索引。
SimpleFSDirectory : 传统的文件系统索引库。
RAMDirectory : 内存索引库
2.2. Document(行)及IndexableField(列)
\
当往索引中加入内容的时候,每一条信息用一个Document来表示,Document的意思表示文档,也可以理解成记录,与关系数据表中的一行数据记录类似;
IndexableField表示字段,与关系数据表中的列类似(列数量不定!!),每个Document也由一系列的IndexableField组成,可以理解为数据库的动态列;
Document提供的方法主要包括:
字段添加:add(Fieldable field)
字段删除:removeField、removeFields
获取字段或值:get、getBinaryValue、getField、getFields等
IndexableField及Field
Field代表Document中的一列数据,相当于一条表记录中的一列。
Lucene提供了一个接口IndexableField,其它的API大多针对这个接口编程,因此Lucene中的列对象实际上是由IndexableField来定义。在实际开发中,主要使用的是Field类的子类。
Field的Store方式及Index方式
Lucene中,在创建Field的时候,可以指定Field的store及index属性;
- store属性:表示字段值是否存储,Store.YES表示要存储,而Store.NO则表示不存储;
- index属性:表示字段的索引方式,
- Tokenized表示根据设定的词法分析器来建立该字段的索引;FALSE,不分词;true要分词。
索引库中实际分为两个部分,一个部分占的空间相对大一些叫做数据区,有Store属性维护,代表是否把字段的内容存到数据区;另一个部分相对小一些,叫做目录区,由Index维护,代表是否支持搜索。
Store和Index组合使用的适用情况见下图:
是否要创建索引: 看是否需要搜索。
是否要分词 : 看是否是专有名词。
是否要存储 : 结果页面,是否要显示,看文档字段的内容能不能链接找到。---大字段
2.3. 分词Analyzer(词法分析器)
分词器是Lucene中非常重要的一个知识点,如果你面试时说你用过Lucene面试官一定会问你用的什么分词器。
分词,也称词法分析器(或者叫语言分析器),就是指索引中的内容按什么样的方式来建立,这在全文检索中非常关键,是按英文单词建立索引,还是按中文词意建立索引;这些需要由Analyzer来指定。
对于中文,需要采用字典分词,也叫词库分词;把中文件的词全部放置到一个词库中,按某种算法来维护词库内容;如果匹配到就切分出来成为词语。通常词库分词被认为是最理想的中文分词算法。如:“我们是中国人”,效果为:“我们”、“中国人”。(可以使用SmartChineseAnalyzer,“极易分词” MMAnalyzer ,或者是“庖丁分词”分词器、IKAnalyzer。推荐使用IKAnalyzer )
在这里我们推荐IKAnalyzer。使用时需导入IKAnalyzer.jar,并且拷贝IKAnalyzer.cfg.xml,ext_stopword.dic文件,分词器测试代码如下:
publicclass AnalyzerTest {
//创建索引的数据 现在写死,以后根据实际应用场景
private String en = "oh my lady gaga"; // oh my god
private String cn = "迅雷不及掩耳盗铃儿响叮当仁不让";
private String str = "源代码教育FullText Search Lucene框架的学习";
/**
* 把特定字符串按特定的分词器来分词
* @param analyzer
* @param str
* @throws Exception
*/
publicvoid testAnalyzer(Analyzer analyzer,String str) throws Exception {
TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(str));
// 在读取词元流后,需要先重置/重加载一次
tokenStream.reset();
while(tokenStream.incrementToken()){
System.out.println(tokenStream);
}
}
//标准分词:不支持中文
@Test
publicvoid testStandardAnalyzer() throws Exception {
testAnalyzer(new StandardAnalyzer(), cn);
}
//简单分词:不支持中文
@Test
publicvoid testSimpleAnalyzer() throws Exception {
testAnalyzer(new SimpleAnalyzer(), cn);
}
//二分分词:两个字是一个词
@Test
publicvoid testCJKAnalyzer() throws Exception {
testAnalyzer(new CJKAnalyzer(), cn);
}
//词典分词:从词典中查找
@Test
publicvoid testSmartChineseAnalyzer() throws Exception {
testAnalyzer(new SmartChineseAnalyzer(), str);
}
//IK分词:从词典中查找
// 简单使用:拷贝两个配置文件,IKAnalyzer.cfg.xml,stopword.dic拷贝一个jar包
IKAnalyzer2012_V5.jar
// 扩展词,停止词
// 注意:打开方式,不要使用其他的,
//直接使用eclipse的text Editor,
修改以后要刷新一下让项目重新编译(有时候需要有时候不需要刷新)
@Test
publicvoid testIKAnalyzer() throws Exception {
//true 粗密度分词(智能分词) false 细密度分词
testAnalyzer(new IKAnalyzer(true), str);
}
}
|
2.4. 索引的添删改
经过之前的分析,我们知道对索引的操作统一使用IndexWriter。测试代码如下:
// 数据源
private String doc1 = "hello world";
private String doc2 = "hello java world";
private String doc3 = "hello lucene world";
// 索引库目录
private String indexPath = "F:\\ecworkspace\\lucene\\indexCRUD";
@Test
publicvoid createIndex() throws IOException, ParseException {
/**
* 准备工作
*/
// 索引目录
Directory d = FSDirectory.open(Paths.get(indexPath));
// 词法分析器
Analyzer analyzer = new StandardAnalyzer();
// 写操作核心配置对象
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
conf.setOpenMode(OpenMode.CREATE);
// 写操作核心对象
IndexWriter indexWriter = new IndexWriter(d, conf);
System.out.println(indexWriter);
/**
* 操作
*/
Document document1 = new Document();
document1.add(new TextField("id", "1", Store.YES));
document1.add(new TextField("name", "doc1", Store.YES));
document1.add(new TextField("content", doc1, Store.YES));
indexWriter.addDocument(document1);
Document document2 = new Document();
document2.add(new TextField("id", "2", Store.YES));
document2.add(new TextField("name", "doc2", Store.YES));
document2.add(new TextField("content", doc2, Store.YES));
indexWriter.addDocument(document2);
Document document3 = new Document();
document3.add(new TextField("id", "3", Store.YES));
document3.add(new TextField("name", "doc3", Store.YES));
document3.add(new TextField("content", doc3, Store.YES));
indexWriter.addDocument(document3);
/**
* 收尾
*/
indexWriter.commit();
indexWriter.close();
searchIndex();
}
@Test
publicvoid del() throws IOException, ParseException{
/**
* 准备工作
*/
// 索引目录
Directory d = FSDirectory.open(Paths.get(indexPath));
// 词法分析器
Analyzer analyzer = new StandardAnalyzer();
// 写操作核心配置对象
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
// 写操作核心对象
IndexWriter indexWriter = new IndexWriter(d, conf);
System.out.println(indexWriter);
//删除所有
//indexWriter.deleteAll();
//第一种
// QueryParser qpParser = new QueryParser("id", analyzer);
// Query query = qpParser.parse("1");
// indexWriter.deleteDocuments(query);
//第二种
indexWriter.deleteDocuments(new Term("id", "1"));
indexWriter.commit();
indexWriter.close();
searchIndex();
}
@Test
publicvoid update() throws IOException, ParseException{
/**
* 准备工作
*/
// 索引目录
Directory d = FSDirectory.open(Paths.get(indexPath));
// 词法分析器
Analyzer analyzer = new StandardAnalyzer();
// 写操作核心配置对象
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
// 写操作核心对象
IndexWriter indexWriter = new IndexWriter(d, conf);
System.out.println(indexWriter);
Document doc = new Document();
doc.add(new TextField("id", "2", Store.YES));
doc.add(new TextField("name", "doc2", Store.YES));
doc.add(new TextField("content", "修改后 -的doc2", Store.YES));
indexWriter.updateDocument(new Term("id","2"), doc );
/*等价于
indexWriter.deleteDocuments(new Term("id", "2"));
indexWriter.addDocument(doc);
*/
indexWriter.commit();
indexWriter.close();
searchIndex();
}
@Test
publicvoid searchIndex() throws IOException, ParseException {
// 索引目录
Directory d = FSDirectory.open(Paths.get(indexPath));
// 词法分析器
Analyzer analyzer = new StandardAnalyzer();
// 创建索引的读写对象
IndexReader r = DirectoryReader.open(d);
// 创建核心对象
IndexSearcher indexSearcher = new IndexSearcher(r);
// 查询解析器
// 参数1:默认查询的字段
// 参数2:分词器
QueryParser queryParser = new QueryParser("content", analyzer);
String queryString = "*:*";
Query query = queryParser.parse(queryString);
// 调用核心对象的search方法
// 参数query: 查询对象
// 参数 n : 前n条
TopDocs topDocs = indexSearcher.search(query, 50);
System.out.println("一共查询到的数量:" + topDocs.totalHits);
// 获得数据集合
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 获取文档ID
intdocId = scoreDoc.doc;
// 通过docId获取Document
Document doc = indexSearcher.doc(docId);
System.out.println("id="+doc.get("id")+",name=" + doc.get("name") + ",content=" + doc.get("content"));
}
}
|
2.5. Query及Searcher
搜索是全文检索中最重要的一部分,前面HelloWorld中也发现,Query对象只是一个接口,他有很多子类的实现。在前面直接使用QueryParser的Parse方法来创建Query对象的实例,实际他会根据我们传入的搜索关键字自动解析成需要的查询类型,索引在这里我们也可以直接new一个Query实例来达到不同的搜索效果:
抽取结构:
// 先做一个准备工作,提供两个search方法
//一个传入搜索关键字进行搜索
public void search(String keyword) throws Exception {
Directory directory = FSDirectory.open(Paths.get("E:\\tools\\eclipse\\workspace\\lucene\\helloIndex"));
;
// 索引的和读取对象
IndexReader reader = DirectoryReader.open(directory);
// 搜索文档通过核心搜索类IndexSearcher来查询
IndexSearcher indexSearcher = new IndexSearcher(reader);
// 先创建一个QueryParse对象
QueryParser queryParser = new QueryParser("content", new StandardAnalyzer());
// 通过queryParse对象解析关键字并创建对应的查询对象
Query query = queryParser.parse(keyword);
// 通过search方法返回前n个文档的封装对象
TopDocs topDocs = indexSearcher.search(query, 5);
// 总共找到的相关的文档数
int totalHits = topDocs.totalHits;
System.out.println("总条数:" + totalHits);
// 获取查询的结果(并不包含文档本身)
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int documentId = scoreDoc.doc;
Document document = indexSearcher.doc(documentId);
float score = scoreDoc.score;
// 获取文档的字段值
String docId = document.get("docId");
String content = document.get("content");
System.out.println("ID:" + documentId + ",score:" + score + ",docId:" + docId + ",content:" + content);
}
}
// 传入一个查询对象
public static void testSearch(Query q) throws Exception {
// 索引库地址
String path = "E:\\work\\eclipse4.7_project\\Luncene-demo\\index";
System.out.println("对应的查询语句为:" + q);
// 获取索引库的目录
Directory d = FSDirectory.open(Paths.get(path));
// 获取索引读取对象
IndexReader reader = DirectoryReader.open(d);
// 创建索引查询器
IndexSearcher searcher = new IndexSearcher(reader);
// 执行查询
TopDocs td = searcher.search(q, 10);
// 遍历结果
for (int i = 0; i < td.scoreDocs.length; i++) {
// 得到符合条件的内部文档对象
ScoreDoc doc = td.scoreDocs[i];
// 得到文档对象
Document d1 = searcher.doc(doc.doc);
System.out.println("title: " + d1.get("title") + " content:" + d1.get("content"));
}
}
|
- 单词查询
- 段落搜索,要想把多个单词当成一个整体进行搜索,使用双引号包裹
- 通配符搜索
- 模糊搜索最多允许 2个错误
- 临近查询,在段落查询的基础上用“~”后面跟一个1到正无穷的正整数。代表段落中,单词与单词之间最大的间隔数
- 组合查询
// + (must) : 对应的单词必须出现
// - (must_not): 不能出现
// 不写 (should): 可能出现
// 关键字之间的逻辑计算是 AND
2.6. 小结
对于lucene API中的方法进行测试,重点掌握各种类型的查询方法。