搜索过程是由Lucene所提供的核心功能之一。下图说明了搜索过程和使用的类。 IndexSearcher是搜索过程中最重要的和核心组件。本章的需要掌握的,了解他们的存储原理后就可以方便知道如何基于这些存储结构来实现高效的搜索。

目录

1. 搜索的基本流程

2. 底层实现原理

2.1 FST

2.2 SkipList

2.3 倒排合并

2.4 结果集排序聚合

3. IndexSearcher

3.1 常用的搜索方法

3.2 DSL语法之QueryParser

3.2 示例


1. 搜索的基本流程

在使用IndexSearcher类的时候,需要一个DirectoryReader和QueryParser,其中DirectoryReader需要对应写入时候的Directory实现。QueryParser主要用来解析你的查询语句,例如你想查 “A and B",lucene内部会有机制解析出是term A和term B的交集查询。

2. 底层实现原理

本节点不展开讲,之前已经分享过反向索引及索引原理,对这些(反向信息)的来龙去脉已经有了一个基本的认识。好吧,那就再范范赘述一遍吧。

2.1 FST

查询过程,在lucene中查询是基于segment。每个segment可以看做是一个独立的subindex,在建立索引的过程中,lucene会不断的flush内存中的数据持久化形成新的segment。多个segment也会不断的被merge成一个大的segment,在老的segment还有查询在读取的时候,不会被删除,没有被读取且被merge的segement会被删除。这个过程类似于LSM数据库的merge过程。

倒排本质上就是基于term的反向列表,方便进行属性查找。为了解决term非常多的问题,lucene里面就引入了term dictonary的概念,也就是term的字典,lucene在这条路上一直在不遗余力的尝试,Lucene3.x先使用跳表,Lucene4.x确实了FST的存储结构(为了解决范围查询或者前缀,后缀等复杂的查询语句)。之前系统的讲过FST在单term查询上可能相比hashmap并没有明显优势,甚至会慢一些。但是在范围,前缀搜索以及压缩率上都有明显的优势。

这里对准实时(写完不一定就可以立即检索到)有了进一步认识了吧!!!

第一步,只解决了快速查找term的所对应的docid。

2.2 SkipList

为了能够快速查找docid,lucene采用了SkipList这一数据结构(空间换时间)。SkipList有以下几个特征:

  • 元素排序的,对应到我们的倒排链,lucene是按照docid进行排序,从小到大。
  • 跳跃有一个固定的间隔,这个是需要建立SkipList的时候指定好,整个SkipList有几层(每层的间隔是多少)

有了跳表,当我们查找一个指定的id时,原来可能需要一个个扫原始链表,先访问低第一层(顶层,level=1),小于进入下一层(level=0),查找顺序是index递增(从左到到右),最终进入原链表(直达找到为止)。

2.3 倒排合并

到这里还有另一个问题,那就是当我们查询中出现了 name='alan' and name='alice' limit 0,20 该如何处理?在我的另一篇中进行讲解

2.4 结果集排序聚合

通过之前介绍可以看出lucene通过倒排的存储模型实现term的搜索,那对于有时候我们需要拿到另一个属性的值进行聚合,或者希望返回结果按照另一个属性进行排序。在lucene4之前需要把结果全部拿到再读取原文进行排序,这样效率较低,还比较占用内存,为了加速lucene实现了fieldcache,把读过的field放进内存中。这样可以减少重复的IO,但是也会带来新的问题,就是占用较多内存。新版本的lucene中引入了DocValues,DocValues是一个基于docid的列式存储。当我们拿到一系列的docid后,进行排序就可以使用这个列式存储,结合一个堆排序进行。当然额外的列式存储会占用额外的空间,lucene在建索引的时候可以自行选择是否需要DocValue存储和哪些字段需要存储。

3. IndexSearcher

相对于索引的创建而言,索引的搜索是使用频繁的。所以 IndexReader 是会经常使用的,所以我们很自然地想到应该将 它设计成一个单例模式。但是索引增加、修改、删除以后,IndexReader 须要重新读取索引信息 , 使用 DirectoryReader 类的静态方法 openIfChanged 就可以达到目的,这个判断会先判断索引是否变更,如果变更,我们要先把原来的 IndexReader 释放。

注意:对于IndexReader来说,IndexReader.open()会产生很大开销(程序是把索引文件全部载入内存)

3.1 常用的搜索方法

IntPoint 搜索数值型

IntPoint.newExactQuery 精确查询,使用的是 PointRangeQuery。
IntPoint.newRangeQuery 范围查询,使用的是 PointRangeQuery。
IntPoint.newSetQuery 集合查询,使用的是 PointInSetQuery。

LongPoint、FloatPoint、DoublePoint 封装的和 IntPoint 都很相似

TermQuery 搜索特定的项

Query query = new TermQuery(new Term(field,value));

TermRangeQuery 搜索特定范围的项

Query query = new TermRangeQuery(field,new BytesRef(start.getBytes()),new BytesRef(end.getBytes()),true,true);

PrefixQuery 前缀匹配搜索

Query query = new PrefixQuery(new Term(field,value));

WildcardQuery 通配符搜索

Query query = new WildcardQuery(new Term(field,value));

FuzzyQuery 模糊匹配搜索

FuzzyQuery query = new FuzzyQuery(new Term(field,value),maxEdits,prefixLength);

BooleanQuery 多个条件的查询

BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
Query query1 = new TermQuery(new Term(field1,value1));
Query query2 = new TermQuery(new Term(field2,value2));
booleanQuery.add(query1,BooleanClause.Occur.MUST);
booleanQuery.add(query2,BooleanClause.Occur.MUST);

PhraseQuery 短语查询

PhraseQuery phraseQuery = new PhraseQuery();
phraseQuery.setSlop(slop);
phraseQuery.add(new Term(field,value1));
phraseQuery.add(new Term(field,value2));

3.2 DSL语法之QueryParser

QueryParser 方式的查询,功能最最强大,几乎涵盖上上面几种方式的查询。QueryParser是通过JavaCC来生成词法分析器和语法分析器的。语法关键字包含:+ - && || ! ( ) { } [ ] ^ " ~ * ? : \

Term-查询词 一种是单一查询词,如"hello",一种是词组(phrase),如"hello world"
Field-查询域 语法如,title:"Do it right",在查询语句中,可以指定从哪个域中寻找查询词,如果不指定,则从默认域中查找。如果title:Do it right,则仅表示在title中查询Do,而it right要在默认域中查询。
Wildcard-通配符查询 ,?表示一个字符,*表示多个字符。通配符可以出现在查询词的中间或者末尾,如te?t,test*,te*t,但决不能出现在开始,如*test,?test。
Fuzzy-模糊查询 模糊查询的算法是基于Levenshtein Distance,也即当两个词的差别小于某个比例的时候,就算匹配,如roam~0.8,即表示差别小于0.2,相似度大于0.8才算匹配。
Proximity-临近查询

在词组后面跟随~10,表示词组中的多个词之间的距离之和不超过10,则满足查询。所谓词之间的距离,即查询词组中词为满足和目标词组相同的最小移动次数。

如索引中有词组"apple boy cat"。如果查询词为"apple boy cat"~0,则匹配;如果查询词为"boy apple cat"~2,距离设为2方能匹配,设为1则不能匹配。

Range-区间查询 一种是包含边界,用[A TO B]指定,一种是不包含边界,用{A TO B}指定。如date:[20020101 TO 20030101]
Boost-增加一个查询词的权重 查询词后面加^N来设定此查询词的权重,默认是1,如果N大于1,则说明此查询词更重要,如果N小于1,则说明此查询词更不重要。如jakarta^4 apache
布尔操作符

AND,OR,和修饰符(NOT,+,-)默认状态下,空格被认为是OR的关系。+表示一个查询语句是必须满足的(required),NOT和-表示一个查询语句是不能满足的(prohibited)。QueryParser.setDefaultOperator(Operator.AND)设置为空格为AND。

组合 可以用括号,将查询语句进行组合,从而设定优先级。如(jakarta OR apache) AND website

传统的解析器:QueryParserMultiFieldQueryParser

新的框架解析器:StandardQueryParser

3.2 示例

public class Search_test {private static String directoryPath = "target";private static Directory directory;private static IndexReader reader;private static IndexSearcher searcher;@BeforeClasspublic static void init() throws IOException {directory = FSDirectory.open(Paths.get(directoryPath));reader = DirectoryReader.open(directory);searcher = new IndexSearcher(reader);//目的是当index文件更新的时候,重新生成IndexReader,否则IndexReader不会更新,除非重启项目。IndexReader changeReader = DirectoryReader.openIfChanged((DirectoryReader) reader);if (changeReader != null) {reader.close();reader = changeReader;searcher = new IndexSearcher(reader); //打开索引}}@AfterClasspublic static void disabled() throws IOException {reader.close();directory.close();}@Testpublic void searchByTerm() throws IOException {// 搜索特定的项Query query = new TermQuery(new Term("id", "5"));showQueryResult(query, 5);}@Testpublic void searchByTermRange() {Query query = new TermRangeQuery("id", new BytesRef("1".getBytes()), new BytesRef("5".getBytes()), true, true);showQueryResult(query, 5);}@Testpublic void searchByPrefix() {Query query = new PrefixQuery(new Term("content", "Apache"));showQueryResult(query, 5);}@Testpublic void searchByWildcard() {Query query = new WildcardQuery(new Term("content", "Apache"));showQueryResult(query, 5);}@Testpublic void searchByBoolean() {BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();Query query1 = new TermQuery(new Term("id", "2"));Query query2 = new TermQuery(new Term("id", "3"));booleanQuery.add(query1, BooleanClause.Occur.MUST);booleanQuery.add(query2, BooleanClause.Occur.MUST);showQueryResult(booleanQuery.build(), 5);}@Testpublic void searchByFuzzy() {FuzzyQuery query = new FuzzyQuery(new Term("content", "search"), 20, 10);showQueryResult(query, 5);}@Testpublic void go_byIntValue() throws IOException {String colName = "intValue";//精确查询Query query = IntPoint.newExactQuery(colName, 5);System.out.println(query.getClass().getName() + ":" + query);showQueryResult(query, 10);//范围查询,不包含边界query = IntPoint.newRangeQuery(colName, Math.addExact(11, 1), Math.addExact(22, -1));showQueryResult(query, 10);//范围查询,包含边界query = IntPoint.newRangeQuery(colName, 11, 22);showQueryResult(query, 10);//范围查询,左包含,右不包含query = IntPoint.newRangeQuery(colName, 11, Math.addExact(22, -1));showQueryResult(query, 10);//集合查询query = IntPoint.newSetQuery(colName, 11, 22, 33);showQueryResult(query, 10);}@Testpublic void go_queryParser() throws IOException, ParseException, QueryNodeException {String defaultFiled = "content";//用法1 传统解析器-单默认字段 QueryParser:Analyzer analyzer = new StandardAnalyzer();QueryParser parser = new QueryParser(defaultFiled, analyzer);Query query = parser.parse("query String");//用法2  传统解析器-多默认字段  MultiFieldQueryParser:String[] multiDefaultFields = {"name", defaultFiled};MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(multiDefaultFields, analyzer);multiFieldQueryParser.setDefaultOperator(QueryParser.Operator.OR);// 设置默认的组合操作,默认是 ORquery = multiFieldQueryParser.parse("笔记本电脑 AND price:1999900");//用法3  新解析框架的标准解析器StandardQueryParser qpHelper = new StandardQueryParser(analyzer);//qpHelper.setAllowLeadingWildcard(true);// 开启第一个字符的通配符匹配,默认关闭因为效率不高// qpHelper.setDefaultOperator(Operator.AND);// 改变空格的默认操作符,以下可以改成ANDquery = qpHelper.parse("(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:1999900", defaultFiled);System.out.println(query);showQueryResult(query, 10);}/*** @param query* @param num   取回前N个文档*/private void showQueryResult(Query query, Integer num) {TopDocs topDocs = null;try {topDocs = searcher.search(query, num);System.out.println("实际搜索到的记录数 => " + topDocs.totalHits);Document document = null;for (ScoreDoc scoreDoc : topDocs.scoreDocs) {document = searcher.doc(scoreDoc.doc);System.out.println("doc:" + document);}} catch (IOException e) {e.printStackTrace();}}
}

总结,搜索的过程比较复杂,掌握里面的算法及存储结构相对的必要,而IndexSearcher的API使用相对而言比较简单,花10分钟了解足矣。这里提问你一个问题lucene怎样去实现“猜你喜欢”?我的另一篇中给出解答。

Lucene系列七:搜索过程和IndexSearcher相关推荐

  1. 理解Lucene索引与搜索过程中的核心类

    理解索引过程中的核心类 执行简单索引的时候需要用的类有: IndexWriter.Directory.Analyzer.Document.Field 1.IndexWriter IndexWriter ...

  2. Lucene学习总结之七:Lucene搜索过程解析

    一.Lucene搜索过程总论 搜索的过程总的来说就是将词典及倒排表信息从索引中读出来,根据用户输入的查询语句合并倒排表,得到结果文档集并对文档进行打分的过程. 其可用如下图示: 总共包括以下几个过程: ...

  3. 2021年大数据ELK(四):Lucene的美文搜索案例

    全网最详细的大数据ELK文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 系列历史文章 美文搜索案例 一.需求 二.准备工作 1.创建IDEA项目 2. ...

  4. Lucene系列之全局搜索引擎入门教程

    Lucene简介 Lucent:Apache软件基金会Jakarta项目组的一个子项目,Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻.在Java开发环境里Lucene是一个成熟 ...

  5. lucene源代码学习之 lucene的经典打分过程

    Lucene中默认的打分模型是VSM(Vector Space Model),其打分公式如下: 看到很多文章都是对这个公式进行解析,但问题的关键在于看了一大段的解析之后,依然不懂其中的细节.我们直接从 ...

  6. lucene索引并搜索mysql数据库[转]

    由于对lucene比较感兴趣,本人在网上找了点资料,终于成功地用lucene对mysql数据库进行索引创建并成功搜索,先总结如下: 首先介绍一个jdbc工具类,用于得到Connection对象: [j ...

  7. Lucene系列(一)什么是Lucene

    前言 上一个系列还没有完结,我又来开新坑啦- 接触搜索/推荐相关工作,也有两年了.工作里对lucene的接触不少,却也不精.最近工作里没有那么忙,因此想通过学习源码的方式,来对lucene进行一个系统 ...

  8. 数学之美 系列七 -- 信息论在信息处理中的应用

    数学之美 系列七 -- 信息论在信息处理中的应用 http://googlechinablog.com/2006/05/blog-post_25.html 我们已经介绍了信息熵,它是信息论的基础,我们 ...

  9. 探究Lucene计算权重的过程

    探究Lucene计算权重的过程 我们知道,影响一个词在一篇文档中的重要性主要有两个因素: 1 term frequency (tf):该词在当前文档出现了多少次,tf越大,说明越重要. 2 docum ...

最新文章

  1. 如何恢复默认域策略和默认域控制器策略
  2. 注册不上zookeeper无报错_Zookeeper 跨区高可用方案
  3. dpi重启后会恢复_Linux 系统的备份恢复
  4. 【漏洞实战】某网站JS文件泄露导致拿到服务器权限
  5. (转)Clang 比 GCC 编译器好在哪里?
  6. 偏标记(partial)学习
  7. lucene.net helper类 【结合盘古分词进行搜索的小例子(分页功能)】
  8. 上海译文公布2019年“新书目录” 名家名译作品结集出版
  9. 《时空幻境》Braid.v1.010.r2-RES-patch
  10. 微带滤波器摘要_微带低通滤波器的设计
  11. hp android 计算器,惠普图形计算器(HP Prime Graphing Calculator)
  12. replay attacker
  13. python开发要学哪些内容_Python开发工程师需要学习哪些内容?
  14. 在线工具:电脑怎么提取图片中的文字?图片如何转化为文字?
  15. WIN10打印机显示服务器脱机,win10网络打印机显示脱机处理方法
  16. android和chrome的发展与未来[j].移动通信,基于Android手机app开发与设计 毕业设计 开题报告...
  17. STM32F103ZE单片机FSMC接口读取NAND Flash芯片K9F1G08U0E的数据时出现数据丢失的解决办法
  18. RaspberryPi 3 B下的64位 uboot linux编译更新
  19. 什么是嵌入式视觉?ARM处理器打造嵌入式视觉硬件
  20. ubuntu安装java17(学习)

热门文章

  1. 机器学习-对数几率回归
  2. system+执行mysql命令_Windows环境下通过MySQL以SYSTEM身份执行系统命令 -电脑资料
  3. Android 欢迎页面 引导页
  4. makedepend 命令
  5. 2021网易游戏研发岗笔试题解及感想
  6. 如何将自己打的jar包放到maven仓库中
  7. 正态分布时的贝叶斯估计
  8. k8s搭建kuboard-v3,手把手教你搭建
  9. 名悦集团:关于汽车安全性,你了解多少
  10. 延边高考成绩查询时间2021,内蒙古高考成绩查询时间2021