Lucene总结系列(一)--认识、helloworld以及基本的api操作。

也是项目需要用的框架之一,为了不让自己轻易忘记它,在此记录一系列的lucene学习笔记(基于lucene4.4,IKAnalyzer2012分词器,只有4.0之前的api才大变,4.0后的api趋于稳定),写完基础篇后,我们就进行结合之前的项目写一系列的实战、优化和原理的文章。

本系列:

(1)SSM框架构建积分系统和基本商品检索系统(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(1)框架整合构建

(2)SSM框架构建积分系统和基本商品检索系统(Spring+SpringMVC+MyBatis+Lucene+Redis+MAVEN)(2)建立商品数据库和Lucene的搭建

(3)Redis系列(一)--安装、helloworld以及读懂配置文件

(4)Redis系列(二)--缓存设计(整表缓存以及排行榜缓存方案实现)


文章结构:(1)认识Lucene;(2)Lucene常用类;(3)Lucene的索引文件目录解析;(4)lucene的helloworld;(5)lucene常用分词器;(6)lucene多条件查询;(7)lucene排序;(8)lucene高亮显示;(9)lucene分页


一、认识Lucene:

Lucene是一套用于全文检索和搜寻的开源程式库是全文检索的框架。lucene其实就做两种工作:一入一出。所谓入是写入,即将你提供的源(本质是字符串)写入索引或者将其从索引中删除;所谓出是读出,即向用户提供全文搜索服务,让用户可以通过关键词定位源。

它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。

生活中的数据总体分为两种:结构化数据和非结构化数据。非结构化数据又一种叫法叫全文数据:

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

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

半结构化数据,如XML,HTML等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。

按照数据的分类,搜索也分为两种:

对结构化数据的搜索:如对数据库的搜索,用SQL语句。再如对元数据的搜索,如利用windows搜索对文件名,类型,修改时间进行搜索等。

对非结构化数据的搜索:如利用windows的搜索也可以搜索文件内容,Linux下的grep命令,再如用Google和百度可以搜索大量内容数据。

对非结构化数据也即对全文数据的搜索主要有两种方法:

(1)顺序扫描法;

比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。如果你有一个80G硬盘,如果想在上面找到一个内容包含某字符串的文件,不花他几个小时,怕是做不到。Linux下的grep命令也是这一种方式。大家可能觉得这种方法比较原始,但对于小数据量的文件,这种方法还是最直接,最方便的。但是对于大量的文件,这种方法就很慢了。

(2)全文检索(先建立索引,再对索引进行搜索的过程)

比如字典,字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。

全文检索大体分两个过程,索引创建(Indexing)和搜索索引(Search)。

索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。

搜索索引:就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。

下图!!首先呈现给用户的就是这种效果,标红字段,标题,概述都是lucene框架所负责的(当然百度有更牛的检索框架)。
这里写图片描述

二、Lucene常用包和类----官方文档

包介绍:

1.Lucene-core.jar,核心包,包括了常用的文档,索引,搜索,存储等相关核心代码。

2.Lucene-analyzers-common.jar,这里面包含了各种语言的词法分析器,用于对文件内容进行关键字切分,提取。

3.Lucene-highlighter.jar,这个jar包主要用于搜索出的内容高亮显示。

4.Lucene-queryparser.jar,提供了搜索相关的代码,用于各种搜索,比如模糊搜索,范围搜索,等等。

本系列使用了个中文分词工具包IKAnalyzer。

类介绍:

描述文字部分参考:这位博主这篇博客

(1)IndexWriter 。索引过程的核心组件。它属于索引创建过程的核心。

增删改索引都是通过indexWriter对象完成。负责创建新索引或者打开已有索引,以及向索引中添加、删除或更新索引文档的信息。可以把IndexWriter看做这样一个对象:提供针对索引文件的写入操作,但不能用于读取或搜索索引。IndexWriter需要开辟一定空间来存储索引,该功能可以由Directory完成。

IndexWriter 的构造函数需要三个参数,第一个参数指定了所创建的索引要存放的位置,他可以是一个 File 对象,也可以是一个 FSDirectory 对象或者 RAMDirectory 对象。后两个参数配置给IndexWriterConfig,然后一起交给indexwriter。第二个参数指定了 Analyzer 类的一个实现,也就是指定这个索引是用哪个分词器对文挡内容进行分词。第三个参数是一个布尔型的变量,如果为 true 的话就代表创建一个新的索引,为 false 的话就代表在原来索引的基础上进行操作。接着程序遍历了目录下面的所有文本文档,并为每一个文本文档创建了一个 Document 对象。然后把文本文档的两个属性:路径和内容加入到了两个 Field 对象中,接着在把这两个 Field 对象加入到 Document 对象中,最后把这个文档用 IndexWriter 类的 add 方法加入到索引中去。这样我们便完成了索引的创建。接下来我们进入在建立好的索引上进行搜索的部分。

(2)Diretory。索引存放的位置。

它是一个抽象类,它的子类负责具体制定索引的存储路径。lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。

就是负责管理这些索引文件,包括数据的读取和写入,以及索引文件的添加,删除和合并。

Lucene的索引体系,支持读共享,写独占的方式来访问索引目录,也就是它允许多个线程实例同时并发的读取,而不允许多个线程同时写入。这种实现的关键就在Diretory锁机制。

还有关注:FSDirectory 对象或者 RAMDirectory 对象

(3)Analyzer

分析器,主要用于分析搜索引擎遇到的各种文本,Analyzer的工作是一个复杂的过程:把一个字符串按某种规则划分成一个个词语,并去除其中的无效词语(停用词),这里说的无效词语如英文中的“of”、“the”,中文中的“的”、“地”等词语,这些词语在文章中大量出现,但是本身不包含什么关键信息,去掉有利于缩小索引文件、提高效率、提高命中率。分词的规则千变万化,但目的只有一个:按语义划分。这点在英文中比较容易实现,因为英文本身就是以单词为单位的,已经用空格分开;而中文则必须以某种方法将连成一片的句子划分成一个个词语。

(4)Document

相当于一个要进行索引的单元,可以是文本文件、字符串或者数据库表的一条记录等等,一条记录经过索引之后,就是以一个Document的形式存储在索引文件,索引的文件都必须转化为Document对象才能进行索引。

因为lucene的“索引库”中是存放Document的。Document中存放的是该Document的field ,而我们要用Java的面向对象编程,不能直接把java的对象直接存放在Lucene的索引库中,所以需要转换下。

lucene的“索引库”你可以抽象成是数据库,Document你可以抽象成是表。那么field可以抽象成表中的各个字段。

(5)Field

Field类是文档索引期间很重要的类,控制着被索引的域值

一个Document可以包含多个信息域,比如一篇文章可以包含“标题”、“正文”等信息域,这些信息域就是通过Field在Document中存储的。你可以类比数据库表里可以有多个字段来理解,虽然两者不能等同,但有助于你理解每个Field包含3部分信息:域的名称,域的类型,域的值。

这个Field还包括一些存储细节:

这里写图片描述

Indexed,表示是否创建索引

Analyzed,表示是否进行分词处理

omitNorms,表示是否忽略域的标准化。其实这个选项有关域的权重计算的,如果你忽略了域的标准化操作,那么在创建索引的时候就不会在域里面多开辟一个字节的空间来存储起加权编码值,因为它会多开辟一个字节的空间,所以会稍微增加了内存占用,如果你的Field都不需要额外的设置权重(注:field.setBoot(1.2)通过这样来设置域的权重值),那么你就可以设置忽略域的标准化操作,即可以减小内存占用,但它也会潜在的影响域的评分。

(6)IndexReader ,打开一个Directory读取索引类。

lucene优点很重要的一点:不要频繁去打开关闭硬盘索引。对于此处,IndexReader的实例化过程是一个非常耗时的过程。

(7)IndexSearcher ,lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具。

1. IndexSearcher提供了对单个IndexReader的查询实现;

2. 通过调用search(Query,n)或者search(Query,Filter,n)方法;

3. 在索引内容变动不大的情况下,我们可以对索引的搜索采用单个IndexSearcher共享的方式来提升性能;

4.如果索引有变动,我们就需要使用DirectoryReader.openIfChanged(DirectoryReader)来获取新的reader,然后创建新的IndexSearcher对象;

5.为了使查询延迟率低,我们最好使用近实时搜索的方法(此时我们的DirectoryReader的构建就要采用DirectoryReader.open(IndexWriter, boolean))

6.IndexSearcher实例是完全线程安全的,这意味着多个线程可以并发调用任何方法。如果需要外部同步,无需添加IndexSearcher的同步;

7.如果我们想提高IndexSearcher的执行效率可以new IndexSearcher(DirecotoryReader,ExcuterService)来创建IndexSearcher对象,这样做的好处为对每块segment采用了分工查询,但是要注意IndexSearcher并不维护ExcuterService的生命周期,我们还需要自行调用ExcuterService的close/awaitTermination

(8)Query ,查询,抽象类,必须通过一系列子类来表述检索的具体需求。

lucene中支持模糊查询,语义查询,短语查询,组合查询等等,如有 TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。

1. TermQuery。搜索索引最基本的查询类型。可以使用单个项构建 TermQuery。项值应该区分大小写,但也并非全是如此。注意,传递的搜索项应该与文档分析得到的项一致,因为分析程序在构建索引之前对原文本执行许多操作。

例如,考虑电子邮件标题 “Job openings for Java Professionals at Bangalore”。假设您使用 StandardAnalyzer 编制索引。现在如果我们使用 TermQuery 搜索 “Java”,它不会返回任何内容,因为本文本应该已经规范化,并通过 StandardAnalyzer 转成小写。如果搜索小写单词 “java”,它将返回所有标题字段中包含该单词的邮

2. RangeQuery范围搜索。索引中的所有项都以字典顺序排列。Lucene 的 RangeQuery 允许用户在某个范围内搜索项。该范围可以使用起始项和最终项(包含两端或不包含两端均可)指定。

3.PrefixQuery前缀搜索。通过前缀单词进行搜索,该方法用于构建一个查询,该查询查找包含以指定单词前缀开始的词汇的文档。

4.BooleanQuery布尔搜索。可以组合任何数量的查询对象,构建强大的查询。它使用 query 和一个关联查询的子句,指示查询是应该发生、必须发生还是不得发生。在 BooleanQuery 中,子句的最大数量默认限制为 1,024。您可以调用 setMaxClauseCount 方法设置最大子句数。

5.WildcardQuery通配符搜索。实现通配符搜索查询,这允许您搜索 arch(可以查找包含 architect、architecture 等)之类的单词。使用两个标准通配符: 表示零个以上;? 表示一个以上。如果使用以通配符查询开始的模式进行搜索,则可能会引起性能的降低,因为这需要查询索引中的所有项以查找匹配文档。

(9)QueryParser。解析用户的查询字符串进行搜索,是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。

对于解析人工输入的查询字符非常有用。您可以使用它将用户输入的查询表达式解析为 Lucene 查询对象,这些对象可以传递到 IndexSearcher 的搜索方法。它可以解析丰富的查询表达式。 QueryParser 内部将人们输入的查询字符串转换为一个具体的查询子类。您需要使用反斜杠(\)将 *、? 等特殊字符进行转义。您可以使用运算符 AND、OR 和 NOT 构建文本布尔值查询。

(10)TopDocs 。根据关键字搜索整个索引库,然后对所有结果进行排序,取指定条目的结果。这就是一个返回的结果集,是包含结果的多个信息的一个对象。其中有totalHits代表记录数,ScoreDoc的数组。。

(11)TokenStream.是一个由分词后的Token结果组成的流,能够不断的得到下一个分成的Token。

1.NumericTokenStream。提供对数字范围的支持

2.SingleTokenTokenStream。此TokenStream仅仅包含一个Token,多用于保存一篇文档仅有一个的信息,如id,如time等,这些信息往往被保存在一个特殊的Token(如ID:ID, TIME:TIME)的倒排表的payload中的,这样可以使用跳表来增加访问速度。

所以SingleTokenTokenStream返回的Token则不是id或者time本身,而是特殊的Token,"ID:ID", "TIME:TIME",而是将id的值或者time的值放入payload中。

3.还有Tokenizer,CharTokenizer,ChineseTokenizer,KeywordTokenizer等等就不多说了,还是各自查文档吧。

(12)ScoreDoc。是代表一个结果的相关度得分与文档编号等信息的对象。这是一个数组,一个怎样的数组??数组的每一位就是包含了我们索引的值、查询记录的评分、索引中文档的位置等等。

(3)Term。搜索的基本单元,一个Term对象有两个String类型的域组成:字段名称和字段的值。

在搜索的时候,我们经常创建Term和TermQuery同时使用,其中第一个参数代表了要在文档哪个Field上进行查找,第二个参数代表要查询的关键词。

三、Lucene的索引文件目录解析:

这里写图片描述
这里写图片描述

(1)Lucene的索引结构是有层次结构的:

1.索引(Index):

在Lucene中一个索引是放在一个文件夹中的。如上图,同一文件夹中的所有的文件构成一个Lucene索引。

2.段(Segment):

一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并。

如上图,具有相同前缀文件的属同一个段,图中共两个段 "_0" 和 "_1"。

segments.gen和segments_5是段的元数据文件,也即它们保存了段的属性信息。

3.文档(Document):

文档是我们建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档。

新添加的文档是单独保存在一个新生成的段中,随着段的合并,不同的文档合并到同一个段中。

4.域(Field):

一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等,都可以保存在不同的域里。

不同域的索引方式可以不同.

5.词(Term):

词是索引的最小单位,是经过词法分析和语言处理后的字符串。

(2)Lucene的索引结构中,即保存了正向信息,也保存了反向信息。

[一] 正向信息:

按层次保存了从索引,一直到词的包含关系:索引(Index) –> 段(segment) –> 文档(Document) –> 域(Field) –> 词(Term)

也就是索引包含了那些段,每个段包含了那些文档,每个文档包含了那些域,每个域包含了那些词。

既然是层次结构,则每个层次都保存了本层次的信息以及下一层次的元信息,也即属性信息,比如一本介绍中国地理的书,应该首先介绍中国地理的概况,以及中国包含多少个省,每个省介绍本省的基本概况及包含多少个市,每个市介绍本市的基本概况及包含多少个县,每个县具体介绍每个县的具体情况。

如上图,包含正向信息的文件有:

segments_N保存了此索引包含多少个段,每个段包含多少篇文档。

XXX.fnm保存了此段包含了多少个域,每个域的名称及索引方式。

XXX.fdx,XXX.fdt保存了此段包含的所有文档,每篇文档包含了多少域,每个域保存了那些信息。.fdt用于存储具有Store.YES属性的Field的数据;.fdt则是一个索引,用于存储Document在.fdt中的位置。

XXX.tvx,XXX.tvd,XXX.tvf保存了此段包含多少文档,每篇文档包含了多少域,每个域包含了多少词,每个词的字符串,位置等信息。

XXX.cfs复合索引格式。在IndexWriter总有一个属性:useCompoundFile, 它的默认值为true, 这个属性的含义为是否使用复合索引格式来保存索引。索引的内容可能会非常大文件数目非常大将极消耗系统资源。因此Lucene提供了一种单文本索引格式,也就是所谓的复合索引格式。使用复合索引格式存储Document内容时,只需要在初始化完一个IndexWriter对象后,使用setUseCompoundFile(boolean)方法。将useCompoundFile的属性值设置为true就可以了。

[二]反向信息:

保存了词典到倒排表的映射:词(Term) –> 文档(Document)

如上图,包含反向信息的文件有:

XXX.tis,XXX.tii保存了词典(Term Dictionary),也即此段包含的所有的词按字典顺序的排序。.tis用于存储分词后的词条(Term), 而.tii就是它的索引文件,它标明了每个.tis文件中的词条位置。。

XXX.frq保存了倒排表,也即包含每个词的文档ID列表。

XXX.prx保存了倒排表中每个词在包含此词的文档中的位置。


四、lucene的helloworld:

Lucene的搭建

五、lucene常用分词器:

展示四个分词器用法:

//分词器:
    @Test
    public void analyzer() throws IOException {
        String txt = "我在学习java";
//        Analyzer analyzer1 = new StandardAnalyzer(Version.LUCENE_44);// 标准分词器
//         Analyzer analyzer2 = new SimpleAnalyzer(Version.LUCENE_44);// 简单分词器
//         Analyzer analyzer3 = new CJKAnalyzer(Version.LUCENE_44);// 二元切分
         Analyzer analyzer4 = new IKAnalyzer(false);// 语意分词
//        TokenStream tokenstream = analyzer1.tokenStream("content", new StringReader(txt));// 生成一个分词流
//         TokenStream tokenstream = analyzer2.tokenStream("content", new StringReader(txt));
//         TokenStream tokenstream = analyzer3.tokenStream("content", new StringReader(txt));
         TokenStream tokenstream = analyzer4.tokenStream("content", new StringReader(txt));
        CharTermAttribute termAttribute = tokenstream.addAttribute(CharTermAttribute.class);// 为token设置属性类
        tokenstream.reset();// 重新设置
        while (tokenstream.incrementToken()) {// 遍历得到token
            System.out.print(new String(termAttribute.buffer(), 0, termAttribute.length()) + "  ");
        }
    }
    /*
    运行结果:
1.我  在  学  习  java  
2.我在学习java  
3.我在  在学  学习  java  
4. 在学  学习  java  
    */

IKAnalyzer更进一步:根据词典去分词,为了让更精准分词,还有个去除标点符号的功能。比如:我在学-*习java,IKAnalyzer分词结果:学习 java

加载扩展词典:mydict.dic
加载扩展停止词典:ext_stopword.dic
在学  学习  java  

我们使用的那两个文件就去下面源码下载看吧,有点大。

<?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">mydict.dic;</entry>
    <!--用户可以在这里配置自己的扩展停用词字典 -->
    <entry key="ext_stopwords">ext_stopword.dic</entry>
</properties>

六、lucene多条件查询: 学自此文章

多条件查询 查询内容必须包含life内容和评分大于等于60分的结果

public void query() throws IOException, ParseException {
        IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引读取类
        IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具类
        String queryStr1 = "java";// 搜索关键字
        BooleanQuery booleanQuery = new BooleanQuery();
        // 条件一内容中必须要有life内容
        QueryParser queryParser = new QueryParser(Version.LUCENE_44, "content", new StandardAnalyzer(Version.LUCENE_44));// 实例查询条件类
        Query query1 = queryParser.parse(queryStr1);
        // 条件二评分大于等于60
        Query query2 = NumericRangeQuery.newIntRange("score", 60, null, true, false);
        booleanQuery.add(query1, BooleanClause.Occur.MUST);
        booleanQuery.add(query2, BooleanClause.Occur.MUST);
        TopDocs topdocs = search.search(booleanQuery, 100);// 查询前100条
        System.out.println("查询结果总数---" + topdocs.totalHits);
        ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有结果集
        for (int i = 0; i < scores.length; i++) {
            int num = scores[i].doc;// 得到文档id
            Document document = search.doc(num);// 拿到指定的文档
            System.out.println("内容====" + document.get("content"));// 由于内容没有存储所以执行结果为null
            System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
        }
}

NumericRangeQuery创建一个查询条件。五个参数分别为字段域、最小值、最大值、是否包含最小值、是否包含最大值。单范围查询,后来看api才发现单范围时可以用null或者把是否包含范围值设为false就行了。

BooleanClause用于表示布尔查询子句关系的类,包括:BooleanClause.Occur.MUST,BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.SHOULD。有以下6种组合:

1. MUST和MUST:取得连个查询子句的交集。

2. MUST和MUST_NOT:表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。

3.MUST_NOT和MUST_NOT:无意义,检索无结果。

4.SHOULD与MUST、SHOULD与MUST_NOT:SHOULD与MUST连用时,无意义,结果为MUST子句的检索结果。与MUST_NOT连用时,功能同MUST。

5.SHOULD与SHOULD:表示“或”关系,最终检索结果为所有检索子句的并集。

七、lucene排序:

lucene有自带的给搜索的每个document进行评分,并且接收排序要求喔!!!

 @Test
    public void defaultSortTest() throws IOException, ParseException {
        IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引读取类
        IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具类
        String queryStr = "Stack";// 搜索关键字
        QueryParser queryParser = new QueryParser(Version.LUCENE_44, "content", new StandardAnalyzer(Version.LUCENE_44));// 实例查询条件类
        Query query = queryParser.parse(queryStr);
        TopDocs topdocs = search.search(query, 100);// 查询前100条
        System.out.println("查询结果总数---" + topdocs.totalHits);
        ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有结果集
        for (int i = 0; i < scores.length; i++) {
            int num = scores[i].doc;// 得到文档id
            Document document = search.doc(num);// 拿到指定的文档
            System.out.println("内容====" + document.get("content"));// 由于内容没有存储所以执行结果为null
            System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
        }
    }
    /*
    结果:
    查询结果总数---4
内容====null
id--2---scors--0.18100528---index---1
内容====null
id--5---scors--0.18100528---index---1
内容====null
id--0---scors--0.09599255---index---1
内容====null
id--3---scors--0.09599255---index---1
    */

search方法,是可以接收排序要求的。

INDEXORDER通过Doc的id进行排序;Sort.RELEVANCE是从积分高到低排序查出

八、lucene高亮:

高亮显示,就是根据用户输入的检索关键字,检索找到该关键字对应的检索结果文件,提取对应于该文件的摘要文本,然后根据设置的高亮格式,将格式写入到摘要文本中对应的与关键字相同或相似的词条上,在网页上显示出来,该摘要中的与关键字有关的文本就会以高亮的格式显示出来。

高亮显示需要几个实现类:

(1)Fragmenter接口。作用是将原始字符串拆分成独立的片段。有三个实现类: NullFragmenter 是该接口的一个具体实现类,它将整个字符串作为单个片段返回,这适合于处理title域和前台文本较短的域,而对于这些域来说,我们是希望在搜索结果中全部展示。SimpleFragmenter 是负责将文本拆分封固定字符长度的片段,但它并处理子边界。你可以指定每个片段的字符长度(默认情况100)但这类片段有点过于简单,在创建片段时,他并不限制查询语句的位置,因此对于跨度的匹配操作会轻易被拆分到两个片段中; SimpleSpanFragmenter 是尝试将让片段永远包含跨度匹配的文档。

(2)(2)Scorer接口。Fragmenter输出的是文本片段序列,而Highlighter必须从中挑选出最适合的一个或多个片段呈现给客户,为了做到这点,Highlighter会要求Scorer来对每个片段进行评分。有两个实现类:QueryTermScorer 基于片段中对应Query的项数进行评分。QueryScorer只对促成文档匹配的实际项进行评分。

(3)Formatter接口。它负责将片段转换成String形式,以及将被高亮显示的项一起用于搜索结果展示以及高亮显示。有两个类:SimpleHTMLFormatter简单的html格式。GradientFormatter复杂型式对不同的得分实现不同的样式。

@Test
    public void highlighter() throws IOException, ParseException, InvalidTokenOffsetsException {
        IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引读取类
        IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具类
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_44);// 分词器
        QueryParser qp = new QueryParser(Version.LUCENE_44, "content", analyzer);// 实例查询条件类
        Query query = qp.parse("Java");
        TopDocs topDocs = search.search(query, 100);// 查询前100条
        System.out.println("共查询出:" + topDocs.totalHits + "条数据");
        ScoreDoc scoreDoc[] = topDocs.scoreDocs;// 结果集
        // 高亮
        Formatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");// 高亮html格式
        Scorer score = new QueryScorer(query);// 检索评份
        Fragmenter fragmenter = new SimpleFragmenter(100);// 设置最大片断为100
        Highlighter highlighter = new Highlighter(formatter, score);// 高亮显示类
        highlighter.setTextFragmenter(fragmenter);// 设置格式
        for (int i = 0; i < scoreDoc.length; i++) {// 遍历结果集
            int docnum = scoreDoc[i].doc;
            Document doc = search.doc(docnum);
            String content = doc.get("content");
            System.out.println(content);// 原内容
            if (content != null) {
                TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(content));
                String str = highlighter.getBestFragment(tokenStream, content);// 得到高亮显示后的内容
                System.out.println(str);
            }
        }
    }

下篇文章讲介绍怎么利用各个lucene模块完成这样的效果啦!!!然后再讲优化。

这里写图片描述

九、lucene分页:

lucene的分页有两种方式:(1)查询出所有结果然后进行分布。(2)通过TopScoreDocCollector.topDocs(int num1,int num2)来实现分页。

 @Test
    public void pageTest() throws IOException, ParseException {
        IndexReader reader = DirectoryReader.open(FSDirectory.open(new File("F:/lucene/index01")));// 索引读取类
        IndexSearcher search = new IndexSearcher(reader);// 搜索入口工具类
        String queryStr = "Java";// 搜索关键字
        QueryParser queryParser = new QueryParser(Version.LUCENE_44, "content", new StandardAnalyzer(Version.LUCENE_44.LUCENE_44));// 实例查询条件类
        Query query = queryParser.parse(queryStr);// 查询
        TopScoreDocCollector results = TopScoreDocCollector.create(100, false);// 结果集
        search.search(query, results);// 查询前100条
        TopDocs topdocs = results.topDocs(1, 2);// 从结果集中第1条开始取2条
        ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有结果集
        for (int i = 0; i < scores.length; i++) {
            int num = scores[i].doc;// 得到文档id
            Document document = search.doc(num);// 拿到指定的文档
            System.out.println("内容====" + document.get("content"));// 由于内容没有存储所以执行结果为null
            System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
        }
    }

一个完整的lucene业务请看本系列下篇以及下方源码。


源码下载:Lucene项目Demo

好了,Lucene总结系列(一)--认识、helloworld以及基本的api操作。讲完了。本博客系列是我做一个项目时候用到的lucene知识,在此拿出来分享经验给大家。欢迎在下面指出错误,共同学习!!你的点赞是对我最好的支持!!

更多内容,可以访问JackFrost的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容