搜索引擎技术系列教材 (一)- lucene - Lucene7 入门
步骤 1 : 关于JDK版本
至少使用JDK8版本,请下载JDK8或者更高版本: 下载以及配置JDK环境
步骤 2 : Lucene 概念
Lucene 这个开源项目,使得 Java开发人员可以很方便地得到像搜索引擎google baidu那样的搜索效果。
步骤 3 : 先运行,看到效果,再学习
老规矩,先下载下载区(点击进入)的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
运行TestLucene类,期望看到如图所示的效果。
一共是10条数据,通过关键字查询出来6条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。
步骤 4 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
步骤 5 : Lucene 版本
当前使用的Lucene版本是截至2018.3.9最新版本 7.2.1
步骤 6 : jar 包
一系列需要的jar包都放在项目里了,直接使用就好了,包括兼容 lucene 7.2.1 的中文分词器
步骤 7 : TestLucene.java
这是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); } } |
步骤 8 : 分词器
准备中文分词器,关于分词器更多概念在分词器概念 中有详细讲解,这里先使用
// 1. 准备中文分词器 IKAnalyzer analyzer = new IKAnalyzer(); |
步骤 9 : 创建索引
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); } |
- 准备数据
- createIndex方法
- addDoc方法
// 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); } |
步骤 10 : 创建查询器
根据关键字 护眼带光源,基于 "name" 字段进行查询。 这个 "name" 字段就是在创建索引步骤里每个Document的 "name" 字段,相当于表的字段名
String keyword = "护眼带光源"; Query query = new QueryParser("name", analyzer).parse(keyword); |
步骤 11 : 执行搜索
接着就执行搜索:
创建索引 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; |
步骤 12 : 显示查询结果
每一个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(); } } |
步骤 13 : 运行结果
如图所示,一共是10条数据,通过关键字查询出来6条命中结果,不同的命中结果有不同的匹配度得分,比如第一条,命中都就很高,既有 护眼, 也有 带光源。 其他的命中度就比较低,没有护眼关键字的匹配,只有光源关键字的匹配。
步骤 14 : 和 like 的区别
like 也可以进行查询,那么使用lucene 的方式有什么区别呢? 主要是两点:
1. 相关度
通过观察运行结果,可以看到不同相关度的结果都会查询出来,但是使用 like,就做不到这一点了
2. 性能
数据量小的时候,like 也会有很好的表现,但是数据量一大,like 的表现就差很多了。 在接下来的教程里会演示对 14万条数据 的查询
步骤 15 : 思路图
现在通过自己做了一遍 Lucene了,有了感性的认识,接着来整理一下做 Lucene的思路。
1. 首先搜集数据
数据可以是文件系统,数据库,网络上,手工输入的,或者像本例直接写在内存上的
2. 通过数据创建索引
3. 用户输入关键字
4. 通过关键字创建查询器
5. 根据查询器到索引里获取数据
6. 然后把查询结果展示在用户面前
搜索引擎技术系列教材 (二)- lucene - Lucene7可以用的中文分词器 IKAnalyzer6.5.0.jar
更多内容,点击了解: https://how2j.cn/k/search-engine/search-engine-analyzer/1673.html
步骤 1 : 分词器概念
分词器指的是搜索引擎如何使用关键字进行匹配,如 入门 中的关键字:护眼带光源。 如果使用like,那么%护眼带光源%,匹配出来的结果就是要么全匹配,要不都不匹配。
而使用分词器,就会把这个关键字分为 护眼,带,光源 3个关键字,这样就可以找到不同相关程度的结果了。
步骤 2 : IKAnalyzer6.5.0.jar
IKAnalyzer 这个分词器很久都没有维护了,也不支持Lucene7。 IKAnalyzer6.5.0.jar 这个是修改之后的的,可以支持Lucene7的jar, 在下载区(点击进入)下载(已经包含在右上角的lucene.rar中了)。
步骤 3 : 代码演示 TestAnalyzer
如代码所示,使用IKAnalyzer 中文分词器就会把 护眼带光源 分为这么如图所示的3个小关键字进行匹配
package com.how2java;
import java.io.IOException;
import org.apache.lucene.analysis.TokenStream; import org.wltea.analyzer.lucene.IKAnalyzer;
public class TestAnalyzer {
public static void main(String[] args) throws IOException {
IKAnalyzer analyzer = new IKAnalyzer(); TokenStream ts= analyzer.tokenStream("name", "护眼带光源"); ts.reset(); while(ts.incrementToken()){ System.out.println(ts.reflectAsString(false)); } } } |
搜索引擎技术系列教材 (三)- lucene - 高亮显示
更多内容,点击了解: https://how2j.cn/k/search-engine/search-engine-hightlight/1677.html
步骤 1 : 先运行,看到效果,再学习
老规矩,先下载下载区(点击进入)的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
如图所示,查询结果里会高亮标记关键字。第一条是匹配度最高的,所有分词都匹配到了,其他的只匹配了少部分分词。
步骤 2 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
步骤 3 : TestLucene
70,71行 81,82行 增加高亮显示
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结果");
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
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) { TokenStream tokenStream = analyzer.tokenStream(f.name(), new StringReader(d.get(f.name()))); String fieldContent = highlighter.getBestFragment(tokenStream, d.get(f.name())); System.out.print("\t" + fieldContent); } System.out.println("<br>"); } }
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); } } |
步骤 4 : 运行结果
运行结果是html代码,为了正常显示,复制到一个html文件里,打开就可以看到效果了
搜索引擎技术系列教材 (四)- lucene - 向Lucene中导入14万条产品数据
更多内容,点击了解: https://how2j.cn/k/search-engine/search-engine-14k/1674.html
步骤 1 : 14万条数据
在前面的 入门 里是用了10条记录来测试,实际情况肯定是不会只有10条记录了,所以为了模仿真实环境,花了很多精力,四处搜刮来了14万条天猫的产品数据,接下来我们就会把这14万条记录加入到 Lucene,然后观察搜索效果。
这14万条记录放在下载区(点击进入) 140k_products.rar,其解析办法在后续会讲解
步骤 2 : 关于数据库
本来应该先把这14万条记录保存进数据库,然后再从数据库中取出来的,不过考虑到不是每个同学都有JDBC基础,以及放进数据库的繁琐,和14万条数据从数据库里读取出来的时间消耗,就改成直接从文件里读取出来,然后转换为泛型是Product的集合的形式,相当于从数据库里读取出来了,不过会快很多。
有兴趣的同学可以自己把这些数据放进数据库里,并且使用 like 的方式看看查询性能如何。
步骤 3 : 先运行,看到效果,再学习
老规矩,先下载下载区(点击进入)的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
执行TestLuceue之后,会花大概20秒左右时间为这14万条记录建立索引,然后输入不同的关键字查询出不同的结果来。
步骤 4 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
步骤 5 : 140k_products.txt
首先下载 140k_products.rar,并解压为140k_products.txt, 然后放在项目目录下。 这个文件里一共有14万条产品记录。
步骤 6 : Product.java
准备实体类来存放产品信息
package com.how2java;
public class Product {
int id; String name; String category; float price; String place;
String code; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public String getPlace() { return place; } public void setPlace(String place) { this.place = place; }
public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Override public String toString() { return "Product [id=" + id + ", name=" + name + ", category=" + category + ", price=" + price + ", place=" + place + ", code=" + code + "]"; }
} |
步骤 7 : ProductUtil.java
准备工具类,把140k_products.txt 文本文件,转换为泛型是Product的集合
package com.how2java;
import java.awt.AWTException; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set;
import org.apache.commons.io.FileUtils;
public class ProductUtil {
public static void main(String[] args) throws IOException, InterruptedException, AWTException {
String fileName = "140k_products.txt";
List<Product> products = file2list(fileName);
System.out.println(products.size());
}
public static List<Product> file2list(String fileName) throws IOException { File f = new File(fileName); List<String> lines = FileUtils.readLines(f,"UTF-8"); List<Product> products = new ArrayList<>(); for (String line : lines) { Product p = line2product(line); products.add(p); } return products; }
private static Product line2product(String line) { Product p = new Product(); String[] fields = line.split(","); p.setId(Integer.parseInt(fields[0])); p.setName(fields[1]); p.setCategory(fields[2]); p.setPrice(Float.parseFloat(fields[3])); p.setPlace(fields[4]); p.setCode(fields[5]); return p; }
} |
步骤 8 : TestLucene.java
在入门中 TestLucene.java 的基础上进行修改。 主要做了两个方面的修改:
1. 索引的增加,以前是10条数据,现在是14万条数据
注: 因为数据量比较大, 所以加入到索引的时间也比较久,请耐心等待。
2. Document以前只有name字段,现在有6个字段
3. 查询关键字从控制台输入,这样每次都可以输入不同的关键字进行查询。 因为索引建立时间比较久,采用这种方式,可以建立一次索引,进行多次查询,否则的话,每次使用不同的关键字,都要耗时建立索引,测试效率会比较低
package com.how2java;
import java.io.IOException; import java.io.StringReader; import java.util.List; import java.util.Scanner;
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. 索引 Directory index = createIndex(analyzer);
// 3. 查询器
Scanner s = new Scanner(System.in);
while(true){ System.out.print("请输入查询关键字:"); String keyword = s.nextLine(); System.out.println("当前关键字是:"+keyword); Query query = new QueryParser( "name", analyzer).parse(keyword);
// 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher=new IndexSearcher(reader); int numberPerPage = 10; 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 + " 个命中.");
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
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) {
if("name".equals(f.name())){ TokenStream tokenStream = analyzer.tokenStream(f.name(), newStringReader(d.get(f.name()))); String fieldContent = highlighter.getBestFragment(tokenStream, d.get(f.name())); System.out.print("\t"+fieldContent); } else{ System.out.print("\t"+d.get(f.name())); } } System.out.println("<br>"); } }
private static Directory createIndex(IKAnalyzer analyzer) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); String fileName = "140k_products.txt"; List<Product> products = ProductUtil.file2list(fileName); int total = products.size(); int count = 0; int per = 0; int oldPer =0; for (Product p : products) { addDoc(writer, p); count++; per = count*100/total; if(per!=oldPer){ oldPer = per; System.out.printf("索引中,总共要添加 %d 条记录,当前添加进度是: %d%% %n",total,per); }
} writer.close(); return index; }
private static void addDoc(IndexWriter w, Product p) throws IOException { Document doc = new Document(); doc.add(new TextField("id", String.valueOf(p.getId()), Field.Store.YES)); doc.add(new TextField("name", p.getName(), Field.Store.YES)); doc.add(new TextField("category", p.getCategory(), Field.Store.YES)); doc.add(new TextField("price", String.valueOf(p.getPrice()), Field.Store.YES)); doc.add(new TextField("place", p.getPlace(), Field.Store.YES)); doc.add(new TextField("code", p.getCode(), Field.Store.YES)); w.addDocument(doc); } } |
搜索引擎技术系列教材 (五)- lucene - 分页查询
更多内容,点击了解: https://how2j.cn/k/search-engine/search-engine-page/1675.html
步骤 1 : 先运行,看到效果,再学习
老规矩,先下载下载区(点击进入)的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
步骤 2 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
步骤 3 : 两种方式
分页查询是很常见的需求,比如要查询第10页,每页10条数据。
Lucene 分页通常来讲有两种方式:
第一种是把100条数据查出来,然后取最后10条。 优点是快,缺点是对内存消耗大。
第二种是把第90条查询出来,然后基于这一条,通过searchAfter方法查询10条数据。 优点是内存消耗小,缺点是比第一种更慢
步骤 4 : 第一种
private static ScoreDoc[] pageSearch1(Query query, IndexSearcher searcher, int pageNow, int pageSize) throws IOException { TopDocs topDocs = searcher.search(query, pageNow*pageSize); System.out.println("查询到的总条数\t"+topDocs.totalHits); ScoreDoc [] alllScores = topDocs.scoreDocs;
List<ScoreDoc> hitScores = new ArrayList<>();
int start = (pageNow -1)*pageSize ; int end = pageSize*pageNow; for(int i=start;i<end;i++) hitScores.add(alllScores[i]);
ScoreDoc[] hits = hitScores.toArray(new ScoreDoc[]{}); return hits; } |
一共查出 pageNow*pageSize条,然后取最后pageSize条
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.TopDocs; 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. 索引 Directory index = createIndex(analyzer);
// 3. 查询器
String keyword = "手机"; System.out.println("当前关键字是:"+keyword); Query query = new QueryParser( "name", analyzer).parse(keyword);
// 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher=new IndexSearcher(reader); int pageNow = 1; int pageSize = 10;
ScoreDoc[] hits = pageSearch1(query, searcher, pageNow, pageSize);
// 5. 显示查询结果 showSearchResults(searcher, hits,query,analyzer); // 6. 关闭查询 reader.close();
}
private static ScoreDoc[] pageSearch1(Query query, IndexSearcher searcher, int pageNow, int pageSize) throws IOException { TopDocs topDocs = searcher.search(query, pageNow*pageSize); System.out.println("查询到的总条数\t"+topDocs.totalHits); ScoreDoc [] alllScores = topDocs.scoreDocs;
List<ScoreDoc> hitScores = new ArrayList<>();
int start = (pageNow -1)*pageSize ; int end = pageSize*pageNow; for(int i=start;i<end;i++) hitScores.add(alllScores[i]);
ScoreDoc[] hits = hitScores.toArray(new ScoreDoc[]{}); return hits; }
private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer) throws Exception { System.out.println("找到 " + hits.length + " 个命中.");
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
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) {
if("name".equals(f.name())){ TokenStream tokenStream = analyzer.tokenStream(f.name(), newStringReader(d.get(f.name()))); String fieldContent = highlighter.getBestFragment(tokenStream, d.get(f.name())); System.out.print("\t"+fieldContent); } else{ System.out.print("\t"+d.get(f.name())); } } System.out.println("<br>"); } }
private static Directory createIndex(IKAnalyzer analyzer) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); String fileName = "140k_products.txt"; List<Product> products = ProductUtil.file2list(fileName); int total = products.size(); int count = 0; int per = 0; int oldPer =0; for (Product p : products) { addDoc(writer, p); count++; per = count*100/total; if(per!=oldPer){ oldPer = per; System.out.printf("索引中,总共要添加 %d 条记录,当前添加进度是: %d%% %n",total,per); }
if(per>10) break;
} writer.close(); return index; }
private static void addDoc(IndexWriter w, Product p) throws IOException { Document doc = new Document(); doc.add(new TextField("id", String.valueOf(p.getId()), Field.Store.YES)); doc.add(new TextField("name", p.getName(), Field.Store.YES)); doc.add(new TextField("category", p.getCategory(), Field.Store.YES)); doc.add(new TextField("price", String.valueOf(p.getPrice()), Field.Store.YES)); doc.add(new TextField("place", p.getPlace(), Field.Store.YES)); doc.add(new TextField("code", p.getCode(), Field.Store.YES)); w.addDocument(doc); } } |
步骤 5 : 第二种
private static ScoreDoc[] pageSearch2(Query query, IndexSearcher searcher, int pageNow, int pageSize) throws IOException { int start = (pageNow - 1) * pageSize; if(0==start){ TopDocs topDocs = searcher.search(query, pageNow*pageSize); return topDocs.scoreDocs; } // 查询数据, 结束页面自前的数据都会查询到,但是只取本页的数据 TopDocs topDocs = searcher.search(query, start); //获取到上一页最后一条
ScoreDoc preScore= topDocs.scoreDocs[start-1];
//查询最后一条后的数据的一页数据 topDocs = searcher.searchAfter(preScore, query, pageSize); return topDocs.scoreDocs;
} |
首先是边界条件,如果是第一页,就直接查询了。
如果不是第一页,那么就取start-1那一条,然后再根据它通过searchAfter 来查询
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.TopDocs; 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. 索引 Directory index = createIndex(analyzer);
// 3. 查询器
String keyword = "手机"; System.out.println("当前关键字是:"+keyword); Query query = new QueryParser( "name", analyzer).parse(keyword);
// 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher=new IndexSearcher(reader); int pageNow = 1; int pageSize = 10;
ScoreDoc[] hits = pageSearch2(query, searcher, pageNow, pageSize);
// 5. 显示查询结果 showSearchResults(searcher, hits,query,analyzer); // 6. 关闭查询 reader.close();
}
private static ScoreDoc[] pageSearch1(Query query, IndexSearcher searcher, int pageNow, int pageSize) throws IOException { TopDocs topDocs = searcher.search(query, pageNow*pageSize); System.out.println("查询到的总条数\t"+topDocs.totalHits); ScoreDoc [] alllScores = topDocs.scoreDocs;
List<ScoreDoc> hitScores = new ArrayList<>();
int start = (pageNow -1)*pageSize ; int end = pageSize*pageNow; for(int i=start;i<end;i++) hitScores.add(alllScores[i]);
ScoreDoc[] hits = hitScores.toArray(new ScoreDoc[]{}); return hits; }
private static ScoreDoc[] pageSearch2(Query query, IndexSearcher searcher, int pageNow, int pageSize) throws IOException {
int start = (pageNow - 1) * pageSize; if(0==start){ TopDocs topDocs = searcher.search(query, pageNow*pageSize); return topDocs.scoreDocs; } // 查询数据, 结束页面自前的数据都会查询到,但是只取本页的数据 TopDocs topDocs = searcher.search(query, start); //获取到上一页最后一条
ScoreDoc preScore= topDocs.scoreDocs[start-1];
//查询最后一条后的数据的一页数据 topDocs = searcher.searchAfter(preScore, query, pageSize); return topDocs.scoreDocs;
}
private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer) throws Exception { System.out.println("找到 " + hits.length + " 个命中.");
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
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) {
if("name".equals(f.name())){ TokenStream tokenStream = analyzer.tokenStream(f.name(), newStringReader(d.get(f.name()))); String fieldContent = highlighter.getBestFragment(tokenStream, d.get(f.name())); System.out.print("\t"+fieldContent); } else{ System.out.print("\t"+d.get(f.name())); } } System.out.println("<br>"); } }
private static Directory createIndex(IKAnalyzer analyzer) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); String fileName = "140k_products.txt"; List<Product> products = ProductUtil.file2list(fileName); int total = products.size(); int count = 0; int per = 0; int oldPer =0; for (Product p : products) { addDoc(writer, p); count++; per = count*100/total; if(per!=oldPer){ oldPer = per; System.out.printf("索引中,总共要添加 %d 条记录,当前添加进度是: %d%% %n",total,per); }
if(per>10) break;
} writer.close(); return index; }
private static void addDoc(IndexWriter w, Product p) throws IOException { Document doc = new Document(); doc.add(new TextField("id", String.valueOf(p.getId()), Field.Store.YES)); doc.add(new TextField("name", p.getName(), Field.Store.YES)); doc.add(new TextField("category", p.getCategory(), Field.Store.YES)); doc.add(new TextField("price", String.valueOf(p.getPrice()), Field.Store.YES)); doc.add(new TextField("place", p.getPlace(), Field.Store.YES)); doc.add(new TextField("code", p.getCode(), Field.Store.YES)); w.addDocument(doc); } } |
搜索引擎技术系列教材 (六)- lucene - 索引删除和更新
更多内容,点击了解: https://how2j.cn/k/search-engine/search-engine-delete-update/1676.html
步骤 1 : 索引删除和更新
索引建立好了之后,还是需要维护的,比如新增,删除和维护。 新增就是建立索引的过程,这里就不表了,本教材主要讲索引的删除和更新。
索引里的数据,其实就是一个一个的Document 对象,那么本文就是介绍如何删除和更新这些Documen对象。
步骤 2 : 先说没有删除前的情形
直接使用14万条数据 里的代码,不过使用不一样的查询语句。
如图所示,通过关键字 “鞭" 可以查询到一条id是51173的数据。
package com.how2java;
import java.io.IOException; import java.io.StringReader; import java.util.List; import java.util.Scanner;
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.index.Term; 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. 索引 Directory index = createIndex(analyzer);
// 3. 查询器
Scanner s = new Scanner(System.in);
while(true){ System.out.print("请输入查询关键字:"); String keyword = s.nextLine(); System.out.println("当前关键字是:"+keyword); Query query = new QueryParser( "name", analyzer).parse(keyword);
// 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher=new IndexSearcher(reader); int numberPerPage = 10; 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 + " 个命中.");
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
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) {
if("name".equals(f.name())){ TokenStream tokenStream = analyzer.tokenStream(f.name(), newStringReader(d.get(f.name()))); String fieldContent = highlighter.getBestFragment(tokenStream, d.get(f.name())); System.out.print("\t"+fieldContent); } else{ System.out.print("\t"+d.get(f.name())); } } System.out.println("<br>"); } }
private static Directory createIndex(IKAnalyzer analyzer) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); String fileName = "140k_products.txt"; List<Product> products = ProductUtil.file2list(fileName); int total = products.size(); int count = 0; int per = 0; int oldPer =0; for (Product p : products) { addDoc(writer, p); count++; per = count*100/total; if(per!=oldPer){ oldPer = per; System.out.printf("索引中,总共要添加 %d 条记录,当前添加进度是: %d%% %n",total,per); }
} writer.close(); return index; }
private static void addDoc(IndexWriter w, Product p) throws IOException { Document doc = new Document(); doc.add(new TextField("id", String.valueOf(p.getId()), Field.Store.YES)); doc.add(new TextField("name", p.getName(), Field.Store.YES)); doc.add(new TextField("category", p.getCategory(), Field.Store.YES)); doc.add(new TextField("price", String.valueOf(p.getPrice()), Field.Store.YES)); doc.add(new TextField("place", p.getPlace(), Field.Store.YES)); doc.add(new TextField("code", p.getCode(), Field.Store.YES)); w.addDocument(doc); } } |
步骤 3 : 删除索引
删除id=51173的Document之后,如图所示,再搜索鞭字,就查询不到结果了。
删除关键代码如下,通过 Term对象删除
//删除id=51173的数据 IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter indexWriter = new IndexWriter(index, config); indexWriter.deleteDocuments(new Term("id", "51173")); indexWriter.commit(); indexWriter.close(); |
- 代码片段
- TestLucene.java
//删除id=51173的数据 IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter indexWriter = new IndexWriter(index, config); indexWriter.deleteDocuments(new Term("id", "51173")); indexWriter.commit(); indexWriter.close(); |
package com.how2java;
import java.io.IOException; import java.io.StringReader; import java.util.List; import java.util.Scanner;
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.index.Term; 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. 索引 Directory index = createIndex(analyzer);
// 3. 查询器
Scanner s = new Scanner(System.in);
//删除id=51173的数据 IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter indexWriter = new IndexWriter(index, config); indexWriter.deleteDocuments(new Term("id", "51173")); indexWriter.commit(); indexWriter.close();
while(true){ System.out.print("请输入查询关键字:"); String keyword = s.nextLine(); System.out.println("当前关键字是:"+keyword); Query query = new QueryParser( "name", analyzer).parse(keyword);
// 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher=new IndexSearcher(reader); int numberPerPage = 10; 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 + " 个命中.");
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
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) {
if("name".equals(f.name())){ TokenStream tokenStream = analyzer.tokenStream(f.name(), newStringReader(d.get(f.name()))); String fieldContent = highlighter.getBestFragment(tokenStream, d.get(f.name())); System.out.print("\t"+fieldContent); } else{ System.out.print("\t"+d.get(f.name())); } } System.out.println("<br>"); } }
private static Directory createIndex(IKAnalyzer analyzer) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); String fileName = "140k_products.txt"; List<Product> products = ProductUtil.file2list(fileName); int total = products.size(); int count = 0; int per = 0; int oldPer =0; for (Product p : products) { addDoc(writer, p); count++; per = count*100/total; if(per!=oldPer){ oldPer = per; System.out.printf("索引中,总共要添加 %d 条记录,当前添加进度是: %d%% %n",total,per); }
} writer.close(); return index; }
private static void addDoc(IndexWriter w, Product p) throws IOException { Document doc = new Document(); doc.add(new TextField("id", String.valueOf(p.getId()), Field.Store.YES)); doc.add(new TextField("name", p.getName(), Field.Store.YES)); doc.add(new TextField("category", p.getCategory(), Field.Store.YES)); doc.add(new TextField("price", String.valueOf(p.getPrice()), Field.Store.YES)); doc.add(new TextField("place", p.getPlace(), Field.Store.YES)); doc.add(new TextField("code", p.getCode(), Field.Store.YES)); w.addDocument(doc); } } |
步骤 4 : 更多删除
还可以按照如下方法来删除索引,API 很明显,就不做代码示例了
DeleteDocuments(Query query):根据Query条件来删除单个或多个Document DeleteDocuments(Query[] queries):根据Query条件来删除单个或多个Document DeleteDocuments(Term term):根据Term来删除单个或多个Document DeleteDocuments(Term[] terms):根据Term来删除单个或多个Document DeleteAll():删除所有的Document |
步骤 5 : 更新索引
如图所示,更新索引后,再用鞭查询,得到的结果是查出了更新之后的数据。 更新的关键代码:
// 更新索引 IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter indexWriter = new IndexWriter(index, config); Document doc = new Document(); doc.add(new TextField("id", "51173", Field.Store.YES)); doc.add(new TextField("name", "神鞭,鞭没了,神还在", 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(); |
package com.how2java;
import java.io.IOException; import java.io.StringReader; import java.util.List; import java.util.Scanner;
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.index.Term; 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. 索引 Directory index = createIndex(analyzer);
// 3. 查询器
// 更新索引 IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter indexWriter = new IndexWriter(index, config); Document doc = new Document(); doc.add(new TextField("id", "51173", Field.Store.YES)); doc.add(new TextField("name", "神鞭,鞭没了,神还在", 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();
Scanner s = new Scanner(System.in); while(true){ System.out.print("请输入查询关键字:"); String keyword = s.nextLine(); System.out.println("当前关键字是:"+keyword); Query query = new QueryParser( "name", analyzer).parse(keyword);
// 4. 搜索 IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher=new IndexSearcher(reader); int numberPerPage = 10; 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 + " 个命中.");
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
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) {
if("name".equals(f.name())){ TokenStream tokenStream = analyzer.tokenStream(f.name(), newStringReader(d.get(f.name()))); String fieldContent = highlighter.getBestFragment(tokenStream, d.get(f.name())); System.out.print("\t"+fieldContent); } else{ System.out.print("\t"+d.get(f.name())); } } System.out.println("<br>"); } }
private static Directory createIndex(IKAnalyzer analyzer) throws IOException { Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter writer = new IndexWriter(index, config); String fileName = "140k_products.txt"; List<Product> products = ProductUtil.file2list(fileName); int total = products.size(); int count = 0; int per = 0; int oldPer =0; for (Product p : products) { addDoc(writer, p); count++; per = count*100/total; if(per!=oldPer){ oldPer = per; System.out.printf("索引中,总共要添加 %d 条记录,当前添加进度是: %d%% %n",total,per); }
} writer.close(); return index; }
private static void addDoc(IndexWriter w, Product p) throws IOException { Document doc = new Document(); doc.add(new TextField("id", String.valueOf(p.getId()), Field.Store.YES)); doc.add(new TextField("name", p.getName(), Field.Store.YES)); doc.add(new TextField("category", p.getCategory(), Field.Store.YES)); doc.add(new TextField("price", String.valueOf(p.getPrice()), Field.Store.YES)); doc.add(new TextField("place", p.getPlace(), Field.Store.YES)); doc.add(new TextField("code", p.getCode(), Field.Store.YES)); w.addDocument(doc); } } |