热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开源搜索引擎Lucene入门

Lucene是一个开源的搜索引擎,或者叫全文索引工具,用于快速查找,而且可以获得一个匹配度得分;比如我们直接使用sql进行搜索的时候,可能会使用Like关键词但是这样有两个不便

Lucene是一个开源的搜索引擎, 或者叫全文索引工具, 用于快速查找, 而且可以获得一个匹配度得分;

比如我们直接使用sql进行搜索的时候, 可能会使用Like关键词

但是这样有两个不便之处, 一是当数据太多时, 会比较慢, 二是这样无法得到一些基于关键词的匹配相似度得分;

用lucene可以实现上面两点;

那么直接开始做一个简单的搜索的demo吧~

一.创建索引

假设现在已经有了一些数据, 放在了一个list里面:

List<String> productNames &#61; new ArrayList<>();
productNames.add("飞利浦led灯泡e27螺口暖白球泡灯家用照明超亮节能灯泡转色温灯泡");
productNames.add("飞利浦led灯泡e14螺口蜡烛灯泡3W尖泡拉尾节能灯泡暖黄光源Lamp");
productNames.add("雷士照明 LED灯泡 e27大螺口节能灯3W球泡灯 Lamp led节能灯泡");
productNames.add("飞利浦 led灯泡 e27螺口家用3w暖白球泡灯节能灯5W灯泡LED单灯7w");
productNames.add("飞利浦led小球泡e14螺口4.5w透明款led节能灯泡照明光源lamp单灯");
productNames.add("飞利浦蒲公英护眼台灯工作学习阅读节能灯具30508带光源");
productNames.add("欧普照明led灯泡蜡烛节能灯泡e14螺口球泡灯超亮照明单灯光源");
productNames.add("欧普照明led灯泡节能灯泡超亮光源e14e27螺旋螺口小球泡暖黄家用");
productNames.add("聚欧普照明led灯泡节能灯泡e27螺口球泡家用led照明单灯超亮光源");
Directory index &#61; createIndex(analyzer, productNames);

最后一行的createIndex方法实现如下:

private static Directory createIndex(IKAnalyzer analyzer, List<String> products) throws IOException {Directory index &#61; new RAMDirectory();IndexWriterConfig config &#61; new IndexWriterConfig(analyzer);IndexWriter writer &#61; new IndexWriter(index, config);for (String name : products) {addDoc(writer, name);}writer.close();return index;
}

这里传入的参数有两个, 第一个是中文分词器, 第二个是存入的数据;

第一步是创建一个Dictionary, 然后构建一个IndexWriter用于写入, 这里要给这个IndexWritter使用一个Config因为需要使用中文分词器对传入的数据进行解析, 然后创建好writer之后循环写入数据即可, 这里的addDoc方法实现如下, 最后关闭writer即可:

private static void addDoc(IndexWriter w, String name) throws IOException {Document doc &#61; new Document();doc.add(new TextField("name", name, Field.Store.YES));w.addDocument(doc);
}

在这里传入一个写入器;
其实这里是创建一个新的Document然后把这个document用writer写入到之前创建的dictionary里面, 此处的document是可以有多个Field的, 但是这里只传入了一项数据, 所以只需要用到一个TextField叫name;

然后到这里为止, 我们的数据部分就准备完成了, 然后下面我们开始执行一次查询

二.执行一次查询


先构建一个查询器

首先我们获取到用户提供的关键词, 然后根据这个关键词来构建一个查询器, 大概代码如下:

String keyword &#61; "护眼带光源";
Query query &#61; new QueryParser("name", analyzer).parse(keyword);

这里我们首先获得了一个keyword, 然后构造了一个QueryParser解析器, 然后传入一个analyzer(就是之前创建的中文分词器), 然后我们要对name进行搜索, 解析keyword, 这样一个搜索的查询器就构建好了;

然后进行搜索

IndexReader reader &#61; DirectoryReader.open(index);
IndexSearcher searcher &#61; new IndexSearcher(reader);
int numberPerPage &#61; 1000;
System.out.printf("当前一共有%d条数据%n",productNames.size());
System.out.printf("查询关键字是&#xff1a;\"%s\"%n",keyword);
ScoreDoc[] hits &#61; searcher.search(query, numberPerPage).scoreDocs;

首先要构造一个IndexReader对之前创建的Dictionary进行读取, 这里调用的是DirectoryReader的静态方法, 然后使用IndexSearcher来搜索, 设定每页要显示多少条数据, 然后调用searcher的search方法, 这里的返回的是一个scoreDoc的数组;

然后显示搜索结果

其实这部分就是要对刚才获得的结果, 也就是那个scoreDoc这个数组进行解析, 获得内容;
大概代码如下:

private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer)throws Exception {System.out.println("找到 " &#43; hits.length &#43; " 个命中.");System.out.println("序号\t匹配度得分\t结果");for (int i &#61; 0; i < hits.length; &#43;&#43;i) {ScoreDoc scoreDoc&#61; hits[i];int docId &#61; scoreDoc.doc;Document d &#61; searcher.doc(docId);List<IndexableField> fields &#61; d.getFields();System.out.print((i &#43; 1));System.out.print("\t" &#43; scoreDoc.score);for (IndexableField f : fields) {System.out.print("\t" &#43; d.get(f.name()));}System.out.println();}
}

可以看到, 这里直接取scoreDoc.doc获得的是在原来的Dictionary中的document id, 然后要利用searcher的doc方法来获得这个document的内容, 然后此处的document中只有一个field就是name, 但是往往可能会有多个field, 所以这里用了遍历所有的field来输出内容, 兼容性更好

整理

所以Lucene的基本流程是这样的:

第一步创建索引
获得数据 → 创建Dictionary
第二步开始查询
查询解析器处理关键词 → 查询器 → IndexSearcher(IndexReader) → ScoreDoc

一些花里胡哨的东西

1.分页查询
这个lucene可以使用分页查询, 可以看到, 在搜索这一步, 有这个操作

ScoreDoc[] hits &#61; searcher.search(query, numberPerPage).scoreDocs;

这一步就是把符合的hits的scoredoc全部存到了这个hits数组里面, 也就相当于保存到了内存中, 这样在数据量较大的时候是不太好的

所以可以使用分页查询, 比如每页10条数据, 一共20页, 我要查第7页的那10条数据

上面的方法就是, 我把这20页的200条数据全部存到内存中, 然后去读取我要的;

但是还可以我先查找到第69条数据, 然后使用searchAfter方法, 查找后面的10条数据, 这样就不用把所有数据都放到内存里了, 给个大概的demo把~

private static ScoreDoc[] pageSearch2(Query query, IndexSearcher searcher, int pageNow, int pageSize)throws IOException{int start &#61; (pageNow-1)*pageSize;if(start &#61;&#61; 0){return searcher.search(query, pageSize).scoreDocs;}TopDocs topDocs &#61; searcher.search(query, start);ScoreDoc preScoreDoc &#61; topDocs.scoreDocs[start-1];topDocs &#61; searcher.searchAfter(preScoreDoc, query, pageSize);return topDocs.scoreDocs;}

2.索引的增加删除修改
Dictionary在创建之后也是可以修改的

增加的方法就和前面创建的时候一样, 还是用writer来写进去add就好了;
删除可以用deleteDocument方法, 类似这样:

//删除id&#61;51173的数据IndexWriterConfig config &#61; new IndexWriterConfig(analyzer);IndexWriter indexWriter &#61; new IndexWriter(index, config);indexWriter.deleteDocuments(new Term("id", "51173"));indexWriter.commit();indexWriter.close();

然后lucene还支持其他的一些方式的删除, 比如这样的:

DeleteDocuments(Query query):根据Query条件来删除单个或多个DocumentDeleteDocuments(Query[] queries):根据Query条件来删除单个或多个DocumentDeleteDocuments(Term term):根据Term来删除单个或多个DocumentDeleteDocuments(Term[] terms):根据Term来删除单个或多个DocumentDeleteAll():删除所有的Document

然后修改也差不多, demo给一个:

IndexWriterConfig config &#61; new IndexWriterConfig(analyzer);
IndexWriter indexWriter &#61; new IndexWriter(index, config);
Document doc &#61; new Document();
doc.add(new TextField("id", "51173", Field.Store.YES));
doc.add(new TextField("name", "神鞭&#xff0c;鞭没了&#xff0c;神还在", Field.Store.YES));
doc.add(new TextField("category", "道具", Field.Store.YES));
doc.add(new TextField("price", "998", Field.Store.YES));
doc.add(new TextField("place", "南海群岛", Field.Store.YES));
doc.add(new TextField("code", "888888", Field.Store.YES));
indexWriter.updateDocument(new Term("id", "51173"), doc );
indexWriter.commit();
indexWriter.close();

更多内容可以到HOW2J网站学习


推荐阅读
  • 在 Asp.net 应用中,动态加载 DropDownList 控件的数据源是一项常见需求。本文探讨了如何高效地从数据库中获取数据,并实时更新下拉列表,确保用户界面始终与后台数据保持同步。通过使用 ADO.NET 和 LINQ to SQL 技术,开发者可以轻松实现这一功能,同时提高应用的性能和用户体验。文中还提供了代码示例和最佳实践,帮助开发者解决常见的数据绑定问题。 ... [详细]
  • 本文详细介绍了 InfluxDB、collectd 和 Grafana 的安装与配置流程。首先,按照启动顺序依次安装并配置 InfluxDB、collectd 和 Grafana。InfluxDB 作为时序数据库,用于存储时间序列数据;collectd 负责数据的采集与传输;Grafana 则用于数据的可视化展示。文中提供了 collectd 的官方文档链接,便于用户参考和进一步了解其配置选项。通过本指南,读者可以轻松搭建一个高效的数据监控系统。 ... [详细]
  • EST:西湖大学鞠峰组污水厂病原菌与土著反硝化细菌是多重抗生素耐药基因的活跃表达者...
    点击蓝字关注我们编译:祝新宇校稿:鞠峰、袁凌论文ID原名:PathogenicandIndigenousDenitrifyingBacte ... [详细]
  • 本文介绍如何使用 Python 的 DOM 和 SAX 方法解析 XML 文件,并通过示例展示了如何动态创建数据库表和处理大量数据的实时插入。 ... [详细]
  • 在安装并配置了Elasticsearch后,我在尝试通过GET /_nodes请求获取节点信息时遇到了问题,收到了错误消息。为了确保请求的正确性和安全性,我需要进一步排查配置和网络设置,以确保Elasticsearch集群能够正常响应。此外,还需要检查安全设置,如防火墙规则和认证机制,以防止未经授权的访问。 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • 在将Web服务器和MySQL服务器分离的情况下,是否需要在Web服务器上安装MySQL?如果安装了MySQL,如何解决PHP连接MySQL服务器时出现的连接失败问题? ... [详细]
  • 本文介绍了如何在 Spring 3.0.5 中使用 JdbcTemplate 插入数据并获取 MySQL 表中的自增主键。 ... [详细]
  • com.sun.javadoc.PackageDoc.exceptions()方法的使用及代码示例 ... [详细]
  • 如何在PHP中获取数组中特定元素的索引位置
    在PHP中获取数组中特定元素的索引位置有多种方法。首先,可以使用 `array_search()` 函数,其语法为 `array_search(目标值, $array)`,该函数将返回匹配元素的第一个键名(即下标)。其次,也可以利用 `array_keys()` 函数,通过 `array_keys($array, 目标值)` 语法来获取所有匹配元素的键名列表。这两种方法都能有效解决数组元素定位的问题,具体选择取决于实际需求和性能考虑。 ... [详细]
  • 在MFC框架中,存在多个全局函数,用于在不同对象间获取信息或创建新对象。其中,`afxGetApp`函数尤为关键,它能够帮助开发者轻松获取当前应用程序的实例指针。本文将详细解析`afxGetApp`函数的内部机制及其在MFC应用程序中的具体应用场景,探讨其在提升代码可维护性和灵活性方面的优势。此外,还将介绍其他常用全局函数如`AfxWinInit()`和`AfxBeginThread()`的功能和使用方法,为开发者提供全面的参考。 ... [详细]
  • Yii framwork 应用小窍门
    Yiiframework应用小窍门1.YiiFramework]如何获取当前controller的名称?下面语句就可以获取当前控制器的名称了!Php代码 ... [详细]
  • 1.0为什么要做这个博客站?  在工作学习中,经常要搜索查找各种各样的资料,每次找到相关资料后都会顺手添加到浏览器书签中,时间一长,书签也就满了。而且下次再点击这个书签时,可能就会忘记当时为什么要添加这个书签了,更有可能书签连接已经无效。这样一来,也就不方便 ... [详细]
  • es的分布式原理?es是如何实现分布式的?
    Elasticsearch设计的理念是分布式搜索引擎,底层其实是基于lucene。核心思 ... [详细]
  • 一:什么是solrSolr是apache下的一个开源项目,使用Java基于lucene开发的全文搜索服务器;Lucene是一个开放源代 ... [详细]
author-avatar
isbool
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有