Lucene 实例教程(四)之检索方法总结

2024-03-16 01:32

本文主要是介绍Lucene 实例教程(四)之检索方法总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自作者:永恒の_☆ 地址:http://blog.csdn.net/chenghui0317/article/details/10281311


最近研究数据库模糊查询,发现oracle数据库中虽然可以用instr来替代like提高效率,但是这个效率提高是有瓶颈的,可以用搜索引擎技术来进一步提高查询效率

一、 前言

       前面简单介绍了如何使用Lucene删除索引和修改索引的操作,这样子避免了索引文件的累加会产生脏数据或者数据重复 ,地址:http://blog.csdn.net/chenghui0317/article/details/10366255

       在项目实战中,这还不能完全解决一些比较棘手的问题,比如:

       1、前面只提供了一种打开索引文件的方式 和 一种 实现全文检索的方式,检索方式的单一性不能根据检索条件权衡筛选最优检索方式;

       2、并且在检索的时候还有一点排序上的瑕疵,最优匹配的document对象并没有显示在最上面, 以及 无法实现对索引结果的操作。

       接下来介绍几中检索索引的方式,比如多字段搜索、多条件搜索、模糊搜索、前缀搜索、使用过滤器过滤查询等等,使用了这些方式之后,可以更好的理解Lucene的执行原理和检索方式的多样化。 其中的好处还需要慢慢去理解和体会。


二、使用Lucene实战


1、通过QueryParser绑定单个字段来检索索引记录

    这是一种最为简单的查询方式,首先封装好QueryParser这个查询转换器,然后根据关键字封装查询对象,这个做好之后交给IndexSearch就可以返回结果了。

具体代码如下:

[java]  view plain copy
print ?
  1. package com.lucene.test;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.text.SimpleDateFormat;  
  6. import java.util.ArrayList;  
  7. import java.util.List;  
  8.   
  9. import org.apache.lucene.analysis.Analyzer;  
  10. import org.apache.lucene.document.Document;  
  11. import org.apache.lucene.index.CorruptIndexException;  
  12. import org.apache.lucene.index.Term;  
  13. import org.apache.lucene.queryParser.MultiFieldQueryParser;  
  14. import org.apache.lucene.queryParser.ParseException;  
  15. import org.apache.lucene.queryParser.QueryParser;  
  16. import org.apache.lucene.search.BooleanClause;  
  17. import org.apache.lucene.search.BooleanQuery;  
  18. import org.apache.lucene.search.Filter;  
  19. import org.apache.lucene.search.FilteredQuery;  
  20. import org.apache.lucene.search.IndexSearcher;  
  21. import org.apache.lucene.search.PrefixQuery;  
  22. import org.apache.lucene.search.Query;  
  23. import org.apache.lucene.search.QueryWrapperFilter;  
  24. import org.apache.lucene.search.ScoreDoc;  
  25. import org.apache.lucene.search.Sort;  
  26. import org.apache.lucene.search.SortField;  
  27. import org.apache.lucene.search.TermQuery;  
  28. import org.apache.lucene.search.TopDocs;  
  29. import org.apache.lucene.search.WildcardQuery;  
  30. import org.apache.lucene.store.Directory;  
  31. import org.apache.lucene.store.SimpleFSDirectory;  
  32. import org.apache.lucene.util.Version;  
  33. import org.wltea.analyzer.lucene.IKAnalyzer;  
  34.   
  35. import com.lucene.entity.Article;  
  36.   
  37. /** 
  38.  * Lucene 检索各种索引的实现方式总结 
  39.  * @author Administrator 
  40.  * 
  41.  */  
  42. public class LuceneSearchDemo {  
  43.       
  44.     public static final String INDEX_DIR_PATH = "indexDir";  
  45.     /* 创建简单中文分析器 创建索引使用的分词器必须和查询时候使用的分词器一样,否则查询不到想要的结果 */  
  46.     private Analyzer analyzer = null;  
  47.     // 索引保存目录  
  48.     private File indexFile = null;  
  49.     //目录对象,因为操作索引文件都要用到它,所以定义为全局变量  
  50.     private Directory directory = null;  
  51.     //索引搜索对象  
  52.     private IndexSearcher indexSearcher;  
  53.       
  54.     /** 
  55.      * 初始化方法 
  56.      * @throws IOException  
  57.      */  
  58.     public void init() throws IOException{  
  59.         analyzer = new IKAnalyzer(true);  
  60.         indexFile = new File(INDEX_DIR_PATH);  
  61.         directory = new SimpleFSDirectory(indexFile);  
  62.         indexSearcher = new IndexSearcher(directory);  
  63.           
  64.         System.out.println("*****************初始化成功**********************");  
  65.     }  
  66.       
  67.     /** 
  68.      * 根据传递的结果集 封装成集合后显示出来 
  69.      * @param scoreDocs 
  70.      * @throws IOException  
  71.      * @throws CorruptIndexException  
  72.      */  
  73.     public void showResult(ScoreDoc[] scoreDocs) throws CorruptIndexException, IOException{  
  74.         List<Article> articles = new ArrayList<Article>();  
  75.         for (int i = 0; i < scoreDocs.length; i++) {  
  76.             int doc = scoreDocs[i].doc;//索引id  
  77.             Document document = indexSearcher.doc(doc);  
  78.               
  79.             Article article = new Article();  
  80.             if (document.get("id") == null) {  
  81.                 System.out.println("id为空");  
  82.             } else {  
  83.                 article.setId(Integer.parseInt(document.get("id")));  
  84.                 article.setTitle(document.get("title"));  
  85.                 article.setContent(document.get("content"));  
  86.                 articles.add(article);  
  87.             }             
  88.         }  
  89.         if(articles.size()!=0){  
  90.             for (Article article : articles) {  
  91.                 System.out.println(article);  
  92.             }  
  93.         }else{  
  94.             System.out.println("没有查到记录。");  
  95.         }  
  96.     }  
  97.   
  98.     /** 
  99.      * 通过QueryParser绑定单个字段来检索索引记录 
  100.      * @param keyword 
  101.      * @throws ParseException  
  102.      * @throws IOException  
  103.      * @throws CorruptIndexException  
  104.      */  
  105.     public void searchByQueryParser(String keyword) throws ParseException, CorruptIndexException, IOException{  
  106.         System.out.println("*****************通过QueryParser来检索索引记录**********************");  
  107.         QueryParser queryParser = new QueryParser(Version.LUCENE_36, "title", analyzer);  
  108.         Query query = queryParser.parse(keyword);  
  109.         // public TopFieldDocs search(Query query, int n, Sort sort)  
  110.         // 参数分别表示 Query查询对象,返回的查询数目,排序对象  
  111.         TopDocs topDocs = indexSearcher.search(query, 10new Sort());  
  112.         showResult(topDocs.scoreDocs);  
  113.     }  
  114.   
  115.     public static void main(String[] args) {  
  116.         LuceneSearchDemo luceneInstance = new LuceneSearchDemo();  
  117.         try {  
  118.             luceneInstance.init();  
  119.             luceneInstance.searchByQueryParser("沪K");  
  120.         } catch (CorruptIndexException e) {  
  121.             e.printStackTrace();  
  122.         } catch (ParseException e) {  
  123.             e.printStackTrace();  
  124.         } catch (IOException e) {  
  125.             e.printStackTrace();  
  126.         }  
  127.     }  
  128. }  
根据mian方法传递的 “沪K”关键字,查询结果如下图:

控制台输出显示都是满足检索条件的结果。

需要注意的是:

    <1> 添加索引时用的什么分词器,那么检索的时候也必须使用这个分词器,否则查询不到任何记录。因为插入索引的时候是按照分词器规则插入的,所以检索的时候会以这种规则为匹配方式;

    <2> 由于次查询方式是使用的标题为查询条件,所以内容中出现的任何字符不会作为匹配元素。如果要使用多字段作为查询条件那么就要使用MultiQueryParser绑定多个字段封装Query查询对象了。


2、通过MultiQueryParser绑定多个字段来检索索引记录

这种实现方式较QueryParser 的区别在于,构造方法中的第二个参数由原先的String变成了String[] ,这就表示可以使用多字段的数组了。

具体代码如下:

[java]  view plain copy
print ?
  1. /** 
  2.  * 通过MultiQueryParser绑定多个字段来检索索引记录 
  3.  * @param keyword 
  4.  * @throws ParseException  
  5.  * @throws IOException  
  6.  * @throws CorruptIndexException  
  7.  */  
  8. public void searchByMultiFieldQueryParser(String keyword) throws ParseException, CorruptIndexException, IOException{  
  9.     System.out.println("*****************通过MultiQueryParser绑定多个字段来检索索引记录**********************");  
  10.     QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_36, new String[]{"title""content"}, analyzer);   
  11.     Query query = queryParser.parse(keyword);  
  12.     Sort sort = new Sort(); //不传参数则按照最优匹配来排序  
  13.     //Sort sort = new Sort(new SortField("title", SortField.STRING, false));   //最后一个参数很关键,默认是降序的,如果要指定为升序改为true即可。  
  14.   
  15.     TopDocs topDocs = indexSearcher.search(query, 10, sort);  
  16.     showResult(topDocs.scoreDocs);  
  17. }  
这样,title和content这两个字段共同作为条件查询检索索引了。运行该方法,使用 “沪K” 关键字,具体效果如下:


这次使用的是多字段检索,所以两个字段都会去匹配,根据上面结果,最优匹配的第一条记录并没有第二条出现的多,可能是这样:

因为是多字段匹配,那么两个字段都要匹配到,上面传递的"沪K"关键字都要在title和content中出现才算最优匹配。所以出现了上面结果。

如果要使用排序可以使用Sort的有参数构造方法,

里面传递SortField的实例,参数个数无上限,需要说明的是它的构造方法

    public SortField(String field, int type, boolean reverse)

第一个是字段名,第二个是字段的类型,第三个是排序的升降序,true表示升序,false表示降序,如果不指定默认是false

现在使用title作为排序字段降序排列,具体效果如下:

上图显示的结果是根据title降序排列的。

需要说明的是:

<1>实际开发如果是全文检索就不建议使用排序,因为排序会导致最优匹配的结果错乱,但是如果是使用单个字段查询的话 倒是没有问题。


3、通过Term绑定字段检索索引记录

这种实现方式主要是通过Term组合查询,具体代码如下:
[java]  view plain copy
print ?
  1. /** 
  2.  * 通过term组合检索索引记录 
  3.  * @param keyword 
  4.  * @throws IOException 
  5.  */  
  6. public void searchByTerm(String keyword) throws IOException{  
  7.     System.out.println("*****************通过term组合检索索引记录**********************");  
  8.     //Term term = new Term("title", keyword);  
  9.     Term term = new Term("content", keyword);  
  10.     //MultiTermQuery  
  11.     Query query = new TermQuery(term);  
  12.       
  13.     TopDocs topDocs = indexSearcher.search(query, 10);  
  14.     showResult(topDocs.scoreDocs);  
  15. }  

实践证明:

这种查询方式有点怪,关键字只能是常用的词组,比如“上海”“中文” 等等,不是常用的词组则什么都不会显示,比如:“文和”“京津沪”等等。

另外 根据前面的经验单个Term绑定单个字段,那么MultiTermQuery就可以绑定多个字段查询了。

使用常用词组作为关键字的查询效果如下:



综合所有的因素考虑:既然用起来不方便所以要慎用。


4、通过wildcard使用通配符组合检索索引记录

这种实现方式主要是可以像sql语句一样使用通配符来查询想要的结果,具体代码如下:

[java]  view plain copy
print ?
  1. /** 
  2.  * 通过wildcard使用通配符组合检索索引记录 
  3.  * @param keyword 
  4.  * @throws IOException 
  5.  */  
  6. public void searchByWildcard(String keyword) throws IOException{  
  7.     System.out.println("*****************通过wildcard使用通配符组合检索索引记录**********************");  
  8.     //Term term = new Term("title", "*" + keyword + "*");  
  9.     //Term term = new Term("content", "*" + keyword + "*");  
  10.     Term term = new Term("content""*" + keyword + "?");  
  11.       
  12.     Query query = new WildcardQuery(term);  
  13.     TopDocs topDocs = indexSearcher.search(query, 10);  
  14.     showResult(topDocs.scoreDocs);  
  15. }  
需要说明的是:

    <1> *代表0个或多个字符,?代表0个或一个字符 ;
    <2> 这种查询方式根据通配符针对中文有效, 对英文和数字完全没有效果,任何英文和数字都不行;
    <3> 该检索方式对空格不敏感,就是说 如果最后一个字符为空格,然后匹配的时候空格不会作为匹配内容。

接下来使用“中文”作为关键字运行的效果如下所示:

其实这种实现方式非常非常的好,但是就是不支持英文和数字,就太不友好了。


5、通过prefix作为前缀组合检索索引记录

[java]  view plain copy
print ?
  1. /** 
  2.  * 通过prefix作为前缀组合检索索引记录 
  3.  * @param keyword 
  4.  * @throws IOException 
  5.  */  
  6. public void searchByPrefix(String keyword) throws IOException{  
  7.     System.out.println("*****************通过prefix作为前缀组合检索索引记录**********************");  
  8.     //Term term = new Term("title", keyword);  
  9.     Term term = new Term("content", keyword);  
  10.     Query query = new PrefixQuery(term);  
  11.       
  12.     TopDocs topDocs = indexSearcher.search(query, 10);  
  13.     showResult(topDocs.scoreDocs);  
  14. }     
实践证明:

    <1>如果使用的分词器是SimpleAnalyzer,那么会严格按照把关键字作为前缀去检索,但是如果使用的分词器是IKAanlayzer,那么会模糊匹配查询

    <2>同样该方式针对中文有效, 对英文和数字完全没有效果,任何英文和数字都不行;

接下来使用“中文”关键字作为检索条件,运行之后的效果如下:

通过前面使用了WildcardQuery通配符的配置之后,现在这种查询方式显得有点多余的感觉。


6、通过filter过滤条件组合检索索引记录

使用这种过滤器先封装查询对象,然后过滤满足条件的结果显示出来,并且这种过滤方式支持多条件,具体代码如下:

[java]  view plain copy
print ?
  1. /** 
  2.  * 通过filter过滤条件组合检索索引记录 
  3.  * @param keywords 
  4.  * @throws IOException 
  5.  */  
  6. public void searchByFilter(String[] keywords) throws IOException{  
  7.     System.out.println("*****************通过filter过滤条件组合检索索引记录**********************");  
  8.     List<Filter> filterList = new ArrayList<Filter>();  
  9.     Term term = new Term("content""*" + keywords[0] + "*");  
  10.       
  11.     Query query = new WildcardQuery(term);  
  12.     //添加过滤器  
  13.     QueryWrapperFilter filter = new QueryWrapperFilter(new WildcardQuery(new Term("content","*" + keywords[1] + "*")));  
  14.     filterList.add(filter);  
  15.     filter = new QueryWrapperFilter(new WildcardQuery(new Term("content","*" + keywords[2] + "*")));  
  16.     filterList.add(filter);  
  17.     for (Filter f : filterList) {  
  18.         query = new FilteredQuery(query, f);  //这里面不断的构造query,传递过滤器封装最终的Query查询对象  
  19.     }  
  20.     TopDocs topDocs = indexSearcher.search(query, 10);  
  21.     showResult(topDocs.scoreDocs);  
  22. }  
这里面首先是使用的一个模糊查询,然后后面添加了两个同样是模糊查询的过滤条件,通过不断创建FilteredQuery的实例来封装Query查询对象,并且只有条件满足的才会显示出来。

如果想看看Query里面到底存放的是什么可以直接输出Query,在这个例子中传递参数 new String[]{"分词","组合","左边"} ,输出的query分别是:

content:*分词*
filtered(content:*分词*)->QueryWrapperFilter(content:*组合*)
filtered(filtered(content:*分词*)->QueryWrapperFilter(content:*组合*))->QueryWrapperFilter(content:*左边*)

接下来运行该方法的效果如下所示:

由此可见,所有的过滤条件都满足了才会被匹配到结果集中显示。


7、通过boolean检索索引记录

这种实现方式主要是讲多个query查询对象进行筛选,这里包括三种方式:

    <1>取出他们共同的部分,即条件都满足的才会匹配;

    <2>取出他们不同的部分,即条件必须不满足的才会匹配;

    <3>取出他们所有部分,即满足任何一个条件即可的就可以匹配。

具体实现代码如下:

[java]  view plain copy
print ?
  1. /** 
  2.  * 通过boolean检索索引记录 
  3.  * @param keyword 
  4.  * @throws IOException 
  5.  */  
  6. public void searchByBoolean(String[] keywords) throws IOException{  
  7.     System.out.println("*****************通过boolean检索索引记录**********************");  
  8.     Query query1 = new WildcardQuery(new Term("content","*" + keywords[0] + "*"));  
  9.     Query query2 = new WildcardQuery(new Term("content","*" + keywords[1] + "*"));  
  10.       
  11.     BooleanQuery query = new BooleanQuery();  
  12.     query.add(query1, BooleanClause.Occur.MUST);  
  13.     query.add(query2, BooleanClause.Occur.MUST);  
  14.     //query.add(query2, BooleanClause.Occur.MUST_NOT);  
  15.     //query.add(query2, BooleanClause.Occur.SHOULD);  
  16.     System.out.println(query);  
  17.     TopDocs topDocs = indexSearcher.search(query, 10);  
  18.     showResult(topDocs.scoreDocs);        
  19. }  

接下来,传递参数 new String[]{"分词","一个"} 运行一下,具体效果如下所示:


其实,booleanQuery的效果和filterQuery效果差不多,都是将所有的Query返回的交集记录整合在一起记录,也就是条件都要满足才行。这里的前提是使用BooleanClause.Occur.MUST这个“必须满足的”参数;

如果是使用 BooleanClause.Occur.SHOULD  返回的是所有的Query返回的并集集合在一起的记录
而使用 BooleanClause.Occur.MUST_NOT 表示 必须没有,这里的查询条件中必须不能出现在最终返回的结果中。


那么,Lucene提供的几种常用的检索方式就介绍完了,最终这些检索方式好不好用还取决于自己去尝试之后,慢慢体会才能体会和领悟。

这篇关于Lucene 实例教程(四)之检索方法总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/813904

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

二分最大匹配总结

HDU 2444  黑白染色 ,二分图判定 const int maxn = 208 ;vector<int> g[maxn] ;int n ;bool vis[maxn] ;int match[maxn] ;;int color[maxn] ;int setcolor(int u , int c){color[u] = c ;for(vector<int>::iter

整数Hash散列总结

方法:    step1  :线性探测  step2 散列   当 h(k)位置已经存储有元素的时候,依次探查(h(k)+i) mod S, i=1,2,3…,直到找到空的存储单元为止。其中,S为 数组长度。 HDU 1496   a*x1^2+b*x2^2+c*x3^2+d*x4^2=0 。 x在 [-100,100] 解的个数  const int MaxN = 3000