- 关于JDK版本
至少使用JDK8版本,请下载JDK8或者更高版本: 下载以及配置JDK环境 - Lucene 概念
Lucene 这个开源项目,使得 Java开发人员可以很方便地得到像搜索引擎google baidu那样的搜索效果。 - 先运行,看到效果,再学习
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
运行TestLucene类,期望看到如图所示的效果。
一共是10条数据,通过关键字查询出来6条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。 - Lucene 版本
当前使用的Lucene版本是截至2018.3.9最新版本 7.2.1 - jar 包
一系列需要的jar包都放在项目里了,直接使用就好了,包括兼容 lucene 7.2.1 的中文分词器 - TestLucene.java
这是TestLucene.java 的完整代码,后续会对代码详细讲解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. 首先准备10条数据
这10条数据都是字符串,相当于产品表里的数据
2. 通过createIndex方法,把它加入到索引当中
创建内存索引,为什么Lucene会比数据库快?因为它是从内存里查,自然就比数据库里快多了呀
根据中文分词器创建配置对象Directory index = new RAMDirectory();
创建索引 writerIndexWriterConfig config = new IndexWriterConfig(analyzer);
遍历那10条数据,把他们挨个放进索引里IndexWriter writer = new IndexWriter(index, config);
每条数据创建一个Document,并把这个Document放进索引里。 这个Document有一个字段,叫做"name"。 TestLucene.java 第49行创建查询器,就会指定查询这个字段for (String name : products) { addDoc(writer, name); }
准备数据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); }
createIndex方法// 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);
addDoc方法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);
- 执行搜索
接着就执行搜索:
创建索引 reader:
基于 reader 创建搜索器:IndexReader reader = DirectoryReader.open(index);
指定每页要显示多少条数据: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;
- 显示查询结果
每一个ScoreDoc[] hits 就是一个搜索结果,首先把他遍历出来
然后获取当前结果的docid, 这个docid相当于就是这个数据在索引中的主键for (int i = 0; i < hits.length; ++i) { ScoreDoc scoreDoc= hits[i];
再根据主键docid,通过搜索器从索引里把对应的Document取出来int docId = scoreDoc.doc;
接着就打印出这个Document里面的数据。 虽然当前Document只有name一个字段,但是代码还是通过遍历所有字段的形式,打印出里面的值,这样当Docment有多个字段的时候,代码就不用修改了,兼容性更好点。Document d = searcher.doc(docId);
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(); } }
- 运行结果
如图所示,一共是10条数据,通过关键字查询出来6条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。 - 和 like 的区别
like 也可以进行查询,那么使用lucene 的方式有什么区别呢? 主要是两点:
1. 相关度
通过观察运行结果,可以看到不同相关度的结果都会查询出来,但是使用 like,就做不到这一点了
2. 性能
数据量小的时候,like 也会有很好的表现,但是数据量一大,like 的表现就差很多了。 在接下来的教程里会演示对 14万条数据 的查询 - 思路图
现在通过自己做了一遍 Lucene了,有了感性的认识,接着来整理一下做 Lucene的思路。
1. 首先搜集数据
数据可以使文件系统,数据库,网络上,手工输入的,或者像本例直接写在内存上的
2. 通过数据创建索引
3. 用户输入关键字
4. 通过关键字创建查询器
5. 根据查询器到索引里获取数据
6. 然后把查询结果展示在用户面前
Lucene-入门
最新推荐文章于 2024-08-16 15:33:24 发布