步骤 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那样的搜索效果。
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
运行TestLucene类,期望看到如图所示的效果。 一共是10条数据,通过关键字查询出来6条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。 采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。 推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。 这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来 这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
当前使用的Lucene版本是截至2018.3.9最新版本 7.2.1
一系列需要的jar包都放在项目里了,直接使用就好了,包括兼容 lucene 7.2.1 的中文分词器
这是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;
每一个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条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。
现在通过自己做了一遍 Lucene了,有了感性的认识,接着来整理一下做 Lucene的思路。
1. 首先搜集数据 数据可以是文件系统,数据库,网络上,手工输入的,或者像本例直接写在内存上的 2. 通过数据创建索引 3. 用户输入关键字 4. 通过关键字创建查询器 5. 根据查询器到索引里获取数据 6. 然后把查询结果展示在用户面前
HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。
问答区域
2020-10-05
和前面同学一样,我也是看了站外的基本概念后来这里再看一遍,思路更清晰了
回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
2019-12-19
仅基于当前教程的看法,欢迎大家指正
回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
2019-05-13
个人想法
2019-04-18
站长,这里创建索引使用分词器,创建查询器用分词器,显示搜索结果又用分词器。这三个步骤都是必须要分词的嘛,还是就是规定。
2019-02-17
明明是9条数据啊。。
提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 1 条以前的提问,请 点击查看
提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
|