Lucene
搜索引擎
1. 运行原理
2. 倒排索引
倒排索引, 又称为反向索引: 以字或者词,甚至是一句话一段话作为一个关键字进行索引, 每一个关键字都会对应着一个记录项, 记录项中记录了这个关键字出现在那些文档中, 已经在此文档的什么位置上
为什么说倒排索引可以提升查询的效率和精准度呢?
倒排索引, 是将数据提前按照格式分词放好,建立索引, 当用户进行搜索, 将用户的关键字进行分词, 然后根据分词后的单词到索引库中寻找对应词条,根据词条, 查到对应所在的文档位置, 将其文档内容直接获取即可
lucene
Lucene是Apache提供的一个开源的全文检索引擎工具包, 其本质就是一个工具包, 而非一个完整的搜索引擎, 但是我们可以通过Lucene来构建一个搜索引擎
1. API
1.1 增删改
IndexWriter: 索引写入器对象
其主要的作用, 添加索引, 修改索引和删除索引
- 创建此对象的时候, 需要传入Directory和indexWriterConfig对象
Directory: 目录类, 用来指定索引库的目录
- 常用的实现类:
- FSDirectory: 用来指定文件系统的目录, 将索引信息保存到磁盘上
- 优点: 索引可以进行长期保存, 安全系数高
- 缺点: 读取略慢
- RAMDriectory: 内存目录, 将索引库信息存放到内存中
- 优点: 读取速度快
- 缺点: 不安全, 无法长期保存, 关机后就消失了
IndexWriterConfig: 索引写入器的配置类
- 创建此对象, 需要传递Lucene的版本和分词器
- 作用:
- 作用1 : 指定Lucene的版本和需要使用的分词器
- 作用2: 设置Lucene的打开索引库的方式: setOpenMode();
//1. 需要创建 indexwriter对象
//1.1 创建 索引库
FSDirectory directory = FSDirectory.open(new File("E:\\test"));
//1.2 创建 写入器配置对象: 参数1 版本号, 参数2 分词器
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST,new StandardAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory,config);
增
Document: 文档
在Lucene中, 每一条数据以文档的形式进行存储, 文档中也有其对应的属性和值, Lucene中一个文档类似数据库的一个表, 表中的字段类似于文档中的字段,只不过这个文档只能保存一条数据
Document看做是一个文件, 文件的属性就是文档的属性, 文件对应属性的值就是文档的属性的值 content
一个文档中可以有多个字段, 每一个字段就是一个field对象,不同的文档可以有不同的属性
字段也有其对应数据类型, 故Field类也提供了各种数据类型的实现类
//2. 写入文档
//2.1 创建文档对象
Document doc = new Document();
//2.2 添加文档的属性
doc.add(new StringField("title","Lucene介绍", Field.Store.YES));
doc.add(new IntField("id",1, Field.Store.YES));
doc.add(new TextField("content","Lucene是一个全文检索的工具包", Field.Store.YES));
indexWriter.addDocument(doc);
//3. 提交数据
indexWriter.commit();
删
// 执行删除操作(根据词条),要求id字段必须是字符串类型
// indexWriter.deleteDocuments(new Term("id", "5"));
// 根据查询条件删除
// indexWriter.deleteDocuments(NumericRangeQuery.newLongRange("id", 2l, 4l, true, false));
// 删除所有
indexWriter.deleteAll();
改
/**
* 更新索引
* 本质先删除再添加
* 先删除所有满足条件的文档,再创建文档
* 因此,更新索引通常要根据唯一字段
*/
// 创建文档对象
Document document = new Document();
document.add(new StringField("id", "9", Store.YES));
document.add(new TextField("title", "谷歌地图之父跳槽FaceBook", Store.YES));
// 索引写入器对象
IndexWriter indexWriter = new IndexWriter(directory, conf);
// 执行更新操作
indexWriter.updateDocument(new Term("id", "1"), document);
// 提交
indexWriter.commit();
// 关闭
indexWriter.close();
1.2 查
- IndexSearcher: Lucene中查询对象, 用来执行查询和排序操作
- 常用方法:
- search(Query query, int n);//执行查询
- 参数1: 查询条件
- 参数2: 返回的最大条数
- search(Query query, int n,Sort sort);
- 参数1: 查询的条件
- 参数2: 返回的最大的条数
- 参数3: 排序
- doc(int id);//根据文档id查询文档对象
- IndexReader: 索引库读取工具
- 使用DirectoryReader来打开索引库
- Query:查询对象
- 获取方式:
- 通过查询解析器
- 单字段的解析器: queryParse
- 多字段的解析器: multiFieldQueryParse
- 使用Lucene自定义的实现类
- Lucene中提供了五种常用的多样化的查询
- TopDocs:查询结果对象
- 第一部分: 查询到的总条数
- int topDocs.totalHits
- 第二部分: 得分文档的数组
- ScoreDoc[] topDocs.scoreDocs;
- ScoreDoc: 得分文档对象
- 第一部分: 文档的id
- topDoc.doc
- 第二部分: 文档的得分
- topDoc.score
//1. 创建查询的核心对象
FSDirectory d = FSDirectory.open(new File("E:\\test"));
IndexReader reader = DirectoryReader.open(d);
IndexSearcher indexSearcher = new IndexSearcher(reader);
单字段查询
//2. 执行查询
QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
Query query = queryParser.parse("全文");
TopDocs topDocs = indexSearcher.search(query, 10);
//3. 获取文档id
ScoreDoc[] scoreDocs = topDocs.scoreDocs; //获取得分文档集合
for (ScoreDoc scoreDoc : scoreDocs) {
int id = scoreDoc.doc;//获取文档id
float score = scoreDoc.score;// 返回此文档的得分
Document doc = indexSearcher.doc(id);
String docId = doc.get("id");
String content = doc.get("content");
System.out.println(docId+" "+content+" "+"得分为:"+score);
特殊查询
词条查询
创建词条对象
注意: 词条是不可在分割的, 词条可以是一个字, 也可以是一句话
使用场景: 主要是针对的是不可在分割的字段, 例如id
由于其不可再分, 可以搜索 全文, 但是不能搜索 全文检索
TermQuery termQuery = new TermQuery(new Term(“content”,“全文”));通配符查询
//通配符:
*: 代表多个字符
?: 代表一个占位符
WildcardQuery wildcardQuery = new WildcardQuery(new Term(“content”,"?uce*"));模糊查询
指的是通过替换, 补位, 移动 能够在二次切换内查询数据即可返回 参数1: term 指定查询的字段和内容
参数2: int n 表示最大编辑的次数 最大2
FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term(“content”,“lucene”),1);
数值范围查询
获取NumericRangeQuery的方式:
通过提供的静态方法获取:
NumericRangeQuery.newIntRange()
NumericRangeQuery.newFloatRange()
NumericRangeQuery.newDoubleRange()
NumericRangeQuery.newLongRange()
参数1: 指定要查询的字段
参数2: 指定要查询的开始值
参数3: 指定要查询的结束值
参数4: 是否包含开始
参数5: 是否包含结束
NumericRangeQuery numericRangeQuery =NumericRangeQuery.newIntRange(“id”,2,4,false,false);
组合查询
Query query1 = NumericRangeQuery.newLongRange("id", 2l, 4l, true, true); Query query2 = NumericRangeQuery.newLongRange("id", 0l, 3l, true, true); // boolean查询本身没有查询条件,它可以组合其他查询 BooleanQuery query = new BooleanQuery(); // 交集: Occur.MUST + Occur.MUST // 并集:Occur.SHOULD + Occur.SHOULD // 非:Occur.MUST_NOT query.add(query1, Occur.SHOULD); query.add(query2, Occur.SHOULD);
lucene其他
1. 高亮显示
// 高亮显示
@Test
public void testHighlighter() throws Exception {
// 目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建读取工具
IndexReader reader = DirectoryReader.open(directory);
// 创建搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
QueryParser parser = new QueryParser("title", new IKAnalyzer());
Query query = parser.parse("谷歌地图");
// 格式化器
Formatter formatter = new SimpleHTMLFormatter("<em>", "</em>");
Scorer scorer = new QueryScorer(query);
// 准备高亮工具
Highlighter highlighter = new Highlighter(formatter, scorer);
// 搜索
TopDocs topDocs = searcher.search(query, 10);
System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 获取文档编号
int docID = scoreDoc.doc;
Document doc = reader.document(docID);
System.out.println("id: " + doc.get("id"));
String title = doc.get("title");
// 用高亮工具处理普通的查询结果,参数:分词器,要高亮的字段的名称,高亮字段的原始值
String hTitle = highlighter.getBestFragment(new IKAnalyzer(), "title", title);
System.out.println("title: " + hTitle);
// 获取文档的得分
System.out.println("得分:" + scoreDoc.score);
}
}
2. 排序
// 排序
@Test
public void testSortQuery() throws Exception {
// 目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建读取工具
IndexReader reader = DirectoryReader.open(directory);
// 创建搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
QueryParser parser = new QueryParser("title", new IKAnalyzer());
Query query = parser.parse("谷歌地图");
// 创建排序对象,需要排序字段SortField,参数:字段的名称、字段的类型、是否反转如果是false,升序。true降序
Sort sort = new Sort(new SortField("id", Type.LONG, true));
// 搜索
TopDocs topDocs = searcher.search(query, 10,sort);
System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 获取文档编号
int docID = scoreDoc.doc;
Document doc = reader.document(docID);
System.out.println("id: " + doc.get("id"));
System.out.println("title: " + doc.get("title"));
}
}
3.分页
@Test
public void page() throws IOException {
FSDirectory directory = FSDirectory.open(new File("e:/test"));
DirectoryReader reader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(reader);
int page=2;
int num=3;
int start=(page-1)*num;
int end=start+num;
Query query1=new WildcardQuery(new Term("content","有点"));
TopDocs topDocs1 = indexSearcher.search(query1, 30);
int totalHits1 = topDocs1.totalHits;
System.out.println("总数"+totalHits1);
int ceil = (int) Math.ceil((totalHits1+0.0) / num);
System.out.println("总页数"+ceil);
// 3 4 5
NumericRangeQuery query = NumericRangeQuery.newIntRange("id", start, end, true, false);
Sort sort = new Sort(new SortField("id", SortField.Type.INT, true));
BooleanQuery query3=new BooleanQuery();
query3.add(query1, BooleanClause.Occur.MUST);
query3.add(query, BooleanClause.Occur.MUST);
TopDocs topDocs = indexSearcher.search(query3, 10,sort);
int totalHits = topDocs.totalHits;
System.out.println("当前页的显示数量"+totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document doc = indexSearcher.doc(docId);
String id = doc.get("id");
String content = doc.get("content");
System.out.println(id+"..."+content);
}
}
4. 加权因子
- Lucene会对搜索的结果的匹配度进行一个加分, 用来表示数据和词条关联性的强弱, 得分越高, 表示匹配度越高, 排名越靠前
- Lucene支持对某一个字段设置加权因子, 来提高其打分, 使其排名更加靠前, 这样当用户搜索的时候, 便可以将此词条对应的文档展示在最前面
TextField textField = new TextField("content",
"学习lucene需要掌握搜索引擎的基本原理和lucene创建索引和查询索引,boots", Store.YES);
textField.setBoost(10);