how2j.cn

下载区
文件名 文件大小
lucene.rar 9m
步骤 1 : 关于JDK版本   
步骤 2 : Lucene 概念   
步骤 3 : 先运行,看到效果,再学习   
步骤 4 : 模仿和排错   
步骤 5 : Lucene 版本   
步骤 6 : jar 包   
步骤 7 : TestLucene.java   
步骤 8 : 分词器   
步骤 9 : 创建索引   
步骤 10 : 创建查询器   
步骤 11 : 执行搜索   
步骤 12 : 显示查询结果   
步骤 13 : 运行结果   
步骤 14 : 和 like 的区别   
步骤 15 : 思路图   

至少使用JDK8版本,请下载JDK8或者更高版本: 下载以及配置JDK环境
Lucene 这个开源项目,使得 Java开发人员可以很方便地得到像搜索引擎google baidu那样的搜索效果。
步骤 3 :

先运行,看到效果,再学习

edit
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
运行TestLucene类,期望看到如图所示的效果。
一共是10条数据,通过关键字查询出来6条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。
先运行,看到效果,再学习
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。

推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
当前使用的Lucene版本是截至2018.3.9最新版本 7.2.1
一系列需要的jar包都放在项目里了,直接使用就好了,包括兼容 lucene 7.2.1 的中文分词器
jar 包
这是TestLucene.java 的完整代码,后续会对代码详细讲解
package com.how2java; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexableField; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.highlight.Highlighter; import org.apache.lucene.search.highlight.QueryScorer; import org.apache.lucene.search.highlight.SimpleHTMLFormatter; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.wltea.analyzer.lucene.IKAnalyzer; public class TestLucene { public static void main(String[] args) throws Exception { // 1. 准备中文分词器 IKAnalyzer analyzer = new IKAnalyzer(); // 2. 索引 List<String> productNames = 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 = createIndex(analyzer, productNames); // 3. 查询器 String keyword = "护眼带光源"; Query query = new QueryParser("name", analyzer).parse(keyword); // 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher = new IndexSearcher(reader); int numberPerPage = 1000; System.out.printf("当前一共有%d条数据%n",productNames.size()); System.out.printf("查询关键字是:\"%s\"%n",keyword); ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs; // 5. 显示查询结果 showSearchResults(searcher, hits, query, analyzer); // 6. 关闭查询 reader.close(); } private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer) throws Exception { System.out.println("找到 " + hits.length + " 个命中."); System.out.println("序号\t匹配度得分\t结果"); for (int i = 0; i < hits.length; ++i) { ScoreDoc scoreDoc= hits[i]; int docId = scoreDoc.doc; Document d = searcher.doc(docId); List<IndexableField> fields = d.getFields(); System.out.print((i + 1)); System.out.print("\t" + scoreDoc.score); for (IndexableField f : fields) { System.out.print("\t" + d.get(f.name())); } System.out.println(); } } private static Directory createIndex(IKAnalyzer analyzer, List<String> products) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); for (String name : products) { addDoc(writer, name); } writer.close(); return index; } private static void addDoc(IndexWriter w, String name) throws IOException { Document doc = new Document(); doc.add(new TextField("name", name, Field.Store.YES)); w.addDocument(doc); } }
准备中文分词器,关于分词器更多概念在分词器概念 中有详细讲解,这里先使用
// 1. 准备中文分词器 IKAnalyzer analyzer = new IKAnalyzer();
// 1. 准备中文分词器
IKAnalyzer analyzer = new IKAnalyzer();
1. 首先准备10条数据
这10条数据都是字符串,相当于产品表里的数据
2. 通过createIndex方法,把它加入到索引当中

创建内存索引,为什么Lucene会比数据库快?因为它是从内存里查,自然就比数据库里快多了呀

Directory index = new RAMDirectory();

根据中文分词器创建配置对象

IndexWriterConfig config = new IndexWriterConfig(analyzer);

创建索引 writer

IndexWriter writer = new IndexWriter(index, config);

遍历那10条数据,把他们挨个放进索引里

for (String name : products) {
addDoc(writer, name);
}

每条数据创建一个Document,并把这个Document放进索引里。 这个Document有一个字段,叫做"name"。 TestLucene.java 第49行创建查询器,就会指定查询这个字段

private static void addDoc(IndexWriter w, String name) throws IOException {
Document doc = new Document();
doc.add(new TextField("name", name, Field.Store.YES));
w.addDocument(doc);
}
// 2. 索引 List<String> productNames = 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 = createIndex(analyzer, productNames);
private static Directory createIndex(IKAnalyzer analyzer, List<String> products) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); for (String name : products) { addDoc(writer, name); } writer.close(); return index; }
private static void addDoc(IndexWriter w, String name) throws IOException { Document doc = new Document(); doc.add(new TextField("name", name, Field.Store.YES)); w.addDocument(doc); }
根据关键字 护眼带光源,基于 "name" 字段进行查询。 这个 "name" 字段就是在创建索引步骤里每个Document的 "name" 字段,相当于表的字段名
String keyword = "护眼带光源"; Query query = new QueryParser("name", analyzer).parse(keyword);
		String keyword = "护眼带光源";
		Query query = new QueryParser("name", analyzer).parse(keyword);
接着就执行搜索:
创建索引 reader:

IndexReader reader = DirectoryReader.open(index);

基于 reader 创建搜索器:

IndexSearcher searcher = new IndexSearcher(reader);

指定每页要显示多少条数据:

int numberPerPage = 1000;

执行搜索

ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;
// 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher = new IndexSearcher(reader); int numberPerPage = 1000; System.out.printf("当前一共有%d条数据%n",productNames.size()); System.out.printf("查询关键字是:\"%s\"%n",keyword); ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;
		// 4. 搜索
		IndexReader reader = DirectoryReader.open(index);
		IndexSearcher searcher = new IndexSearcher(reader);
		int numberPerPage = 1000;
		System.out.printf("当前一共有%d条数据%n",productNames.size());
		System.out.printf("查询关键字是:\"%s\"%n",keyword);
		ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;
步骤 12 :

显示查询结果

edit
每一个ScoreDoc[] hits 就是一个搜索结果,首先把他遍历出来

for (int i = 0; i < hits.length; ++i) {
ScoreDoc scoreDoc= hits[i];

然后获取当前结果的docid, 这个docid相当于就是这个数据在索引中的主键

int docId = scoreDoc.doc;

再根据主键docid,通过搜索器从索引里把对应的Document取出来

Document d = searcher.doc(docId);

接着就打印出这个Document里面的数据。 虽然当前Document只有name一个字段,但是代码还是通过遍历所有字段的形式,打印出里面的值,这样当Docment有多个字段的时候,代码就不用修改了,兼容性更好点。
scoreDoc.score 表示当前命中的匹配度得分,越高表示匹配程度越高

List<IndexableField> fields = d.getFields();
System.out.print((i + 1));
System.out.print("\t" + scoreDoc.score);
for (IndexableField f : fields) {
System.out.print("\t" + d.get(f.name()));
}
private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer) throws Exception { System.out.println("找到 " + hits.length + " 个命中."); System.out.println("序号\t匹配度得分\t结果"); for (int i = 0; i < hits.length; ++i) { ScoreDoc scoreDoc= hits[i]; int docId = scoreDoc.doc; Document d = searcher.doc(docId); List<IndexableField> fields = d.getFields(); System.out.print((i + 1)); System.out.print("\t" + scoreDoc.score); for (IndexableField f : fields) { System.out.print("\t" + d.get(f.name())); } System.out.println(); } }
	private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer)
			throws Exception {
		System.out.println("找到 " + hits.length + " 个命中.");
		System.out.println("序号\t匹配度得分\t结果");
		for (int i = 0; i < hits.length; ++i) {
			ScoreDoc scoreDoc= hits[i];
			int docId = scoreDoc.doc;
			Document d = searcher.doc(docId);
			List<IndexableField> fields = d.getFields();
			System.out.print((i + 1));
			System.out.print("\t" + scoreDoc.score);
			for (IndexableField f : fields) {
				System.out.print("\t" + d.get(f.name()));
			}
			System.out.println();
		}
	}
如图所示,一共是10条数据,通过关键字查询出来6条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。
运行结果
步骤 14 :

和 like 的区别

edit
like 也可以进行查询,那么使用lucene 的方式有什么区别呢? 主要是两点:
1. 相关度
通过观察运行结果,可以看到不同相关度的结果都会查询出来,但是使用 like,就做不到这一点了
2. 性能
数据量小的时候,like 也会有很好的表现,但是数据量一大,like 的表现就差很多了。 在接下来的教程里会演示对 14万条数据 的查询
现在通过自己做了一遍 Lucene了,有了感性的认识,接着来整理一下做 Lucene的思路。
1. 首先搜集数据
数据可以是文件系统,数据库,网络上,手工输入的,或者像本例直接写在内存上的
2. 通过数据创建索引
3. 用户输入关键字
4. 通过关键字创建查询器
5. 根据查询器到索引里获取数据
6. 然后把查询结果展示在用户面前
思路图


HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。


问答区域    
2020-10-05 和前面同学一样,我也是看了站外的基本概念后来这里再看一遍,思路更清晰了
sparksun007

不是说站长不懂,确实很多基本概念这里解释的不清晰,对初学者真的很晦涩。我参考的这篇博文: https://blog.csdn.net/weixin_42633131/article/details/82873731很基础,补充了how2j.cn的基本概念解释,这样一看就知道每个类是做什么的,然后知道倒排索引技术后,对各种索引的建立、查询流程就很清晰了,代码逻辑性就更容易读懂和编写了,希望站长在编写教程时能吸收下教学方法吧,没有埋怨的意思,确实是有个感想希望您能理解,我们都想学好学通,相信站长也是想讲通~我个人经历认为这样能帮助后来人学习更大一些,站长希望参考借鉴下吧!




1 个答案

gjian707
答案时间:2021-06-24
好人一生平安



回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到




2019-12-19 仅基于当前教程的看法,欢迎大家指正
hx1176406648




Documents 类里面有句话:Documents are the unit of indexing and search.搜索就是基于这些Document ,Document是一组字段,字段里存储要查询的数据。 通过分词器创建索引输出流配置对象,然后创建索引输出流,索引输出流是用来将数据写入索引的。每个数据创建一个文本字段,将这个字段放进Document里面,再将Document写进索引。 通过DirectoryReader打开该索引的索引输入流,基于索引输入流创建搜索器,根据查询器进行搜索得到结果ScoreDoc数组。 在索引中每个Document都有唯一标识符,而ScoreDoc中存有该唯一标识符以及该条Document中某个指定查询字段与查询关键字的匹配命中度得分。
加载中

							

							





回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到





2019-05-13 个人想法
2019-04-18 站长,这里创建索引使用分词器,创建查询器用分词器,显示搜索结果又用分词器。这三个步骤都是必须要分词的嘛,还是就是规定。
2019-02-17 明明是9条数据啊。。


提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 1 条以前的提问,请 点击查看

提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
关于 工具和中间件-搜索引擎技术-入门 的提问

尽量提供截图代码异常信息,有助于分析和解决问题。 也可进本站QQ群交流: 578362961
提问尽量提供完整的代码,环境描述,越是有利于问题的重现,您的问题越能更快得到解答。
对教程中代码有疑问,请提供是哪个步骤,哪一行有疑问,这样便于快速定位问题,提高问题得到解答的速度
在已经存在的几千个提问里,有相当大的比例,是因为使用了和站长不同版本的开发环境导致的,比如 jdk, eclpise, idea, mysql,tomcat 等等软件的版本不一致。
请使用和站长一样的版本,可以节约自己大量的学习时间。 站长把教学中用的软件版本整理了,都统一放在了这里, 方便大家下载: https://how2j.cn/k/helloworld/helloworld-version/1718.html

上传截图