****************************************************************************************************************************************************************************
1、概述
【1】入门:作用、有点与缺点
【2】应用:索引、搜索、field域使用、索引库、分词器、高级搜岁实战
【3】高级:底层存储结构、词典排序算法、优化、使用的一些使用注意事项
****************************************************************************************************************************************************************************
2、Lucene的作用
【1】用户—>服务器—>Lucene API—>索引库—>数据库/文档/web网页—>再返回。
****************************************************************************************************************************************************************************
3、常用的查询算法
【1】顺序扫描法:(数据量大的时候就GG),mysql的like查询就是,文本编辑器的Ctrl+F。
【2】倒排索引:把文章提取出来—>文档(正文)—>切分词组成索引目录。查询的时候先查目录,然后再找正文。切分词是个关键。
为什么倒排索引快?去掉重复的词,去掉停用词(的、地、得、a、an、the)。查字词典肯定比文章少。字典原理所以快。
优点:准确率高、速度快。但是空间占用量肯定会大,时间与空间不能兼得。它是用空间换时间。额外占用磁盘空间来存储目录。
****************************************************************************************************************************************************************************
5、全文检索技术使用场景
【1】站内搜索(百度贴吧、京东、淘宝)。垂直领域的搜索(818工作网)。专业搜索引擎(谷歌、百度)
****************************************************************************************************************************************************************************
6、什么是Lucene
【1】文章—>词—>索引(目录)
【2】全文检索:查先查目录,再查文本,这就是全文检索。
【3】Doug Cutting是Lucene、Nutch、Hadoop等项目的发起人。捐献给了Apache基金会。
【4】官网 https://lucene.apache.org
****************************************************************************************************************************************************************************
7、索引和搜索流程概述
【1】原始文档—>创建索引(获得文档-构建文档对象-分词-创建索引)—>索引库(肯定是提前创建)。
【2】用户查询—>创建查询—>执行查询—>渲染结果—>返回结果。
****************************************************************************************************************************************************************************
8、Lucene索引流程详细
【1】Document文档(唯一ID)。Field域(key value的形式)。id:1 name:华为手机64G brandName:华为。id:2 name:华为手机128G brandName:华为
【2】会根据text提取分词,分析后得到的词:....................。关键词 1 在文档1,关键词2 在文档2 ,关键词手机 在文档1&文档2。这种方式存储。
【3】然后先找到关键词在哪个文档,然后再去对应文档查,有道理呀。卧槽
****************************************************************************************************************************************************************************
9、Lucene搜索流程详细
【1】华为手机,看是华为 AND 手机,还是华为 OR 手机。来决定查询结果
****************************************************************************************************************************************************************************
10、Lucene入门
【1】jar包下载配置
<!--Lucene****************************************************************************************************************************************************-->
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-highlighter -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>7.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-smartcn -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>7.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-common -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>7.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>7.7.2</version>
</dependency>
****************************************************************************************************************************************************************************
13、创建索引
【1】具体创建代码
public static void createIndex() throws Exception {
// 1.采集数据
Product product_1 = new Product(1, "华为手机", 3000, 10, "华为.jpg", "华为", "300*300", 5);
Product product_2 = new Product(2, "苹果手机", 8000, 30, "苹果.jpg", "苹果", "500*500", 15);
List<Product> productList = new ArrayList<>();
productList.add(product_1);
productList.add(product_2);
List<Document> documentList = new ArrayList<>(); // 文档集合
// 2.创建文档对象
for (Product temp : productList) {
// 创建文档
Document document = new Document();
// 创建域对象,并且放到文档对象中
document.add(new TextField("id", temp.getId() + "", Field.Store.YES));
document.add(new TextField("name", temp.getName(), Field.Store.YES));
document.add(new TextField("price", temp.getPrice() + "", Field.Store.YES));
document.add(new TextField("num", temp.getNum() + "", Field.Store.YES));
document.add(new TextField("image", temp.getImage() + "", Field.Store.YES));
document.add(new TextField("brandName", temp.getBrandName() + "", Field.Store.YES));
document.add(new TextField("spec", temp.getSpec() + "", Field.Store.YES));
// 放到文档集合
documentList.add(document);
}
// 3.创建分词器
Analyzer analyzer = new StandardAnalyzer();
// 4.创建index目录对象,目录对象表示索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建IndexWriterConfig对象, 这个对象指定切分词使用的分词器
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
// 6.创建IndexWriter输出流对象,指定输出位置和使用的config初始化对象。
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 7.写入文档到索引库
for (Document temp : documentList) {
indexWriter.addDocument(temp);
}
// 8.释放资源
indexWriter.close();
}
****************************************************************************************************************************************************************************
14、查看索引详情工具
【1】https://github.com/DmitryKey/luke/tree/luke-swing-8.0.0/src/main/java/org/apache/lucene/luke/app/desktop直接下载运行
【2】Luke工具的使用,很重要的!!!!!!!!!!!!
****************************************************************************************************************************************************************************
15、搜索索引
【1】实际代码
// 二、搜索
public static void searchIndex() throws Exception {
// 1.创建分词器(对搜索的内容进行分词使用)。如华为手机可能拆分为 华为 手机
Analyzer analyzer = new StandardAnalyzer();
// 注意!!!:分词器要和创建索引的时候使用的分词器一模一样(不然搜索的时候就有问题)
// 2.创建查询对象 // 第一个arg默认查询域 //
QueryParser queryParser = new QueryParser("name", analyzer);
// 3.设置搜索关键词
Query query = queryParser.parse("华为"); // queryParser.parse("id:华为手机") 指定从id查,不指定就从默认的name域查
// 4.设置Directory目录对象,指定索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建输入流对象
IndexReader indexReader = DirectoryReader.open(directory);
// 6.创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
// 7.搜索并返回结果
TopDocs topDocs_10 = indexSearcher.search(query, 10);
// 8.获取结果集
ScoreDoc[] scoreDocArray = topDocs_10.scoreDocs;
// 9.遍历结果集
System.out.println("共查询到 " + scoreDocArray.length + " 条数据");
if (scoreDocArray != null) {
for (ScoreDoc temp : scoreDocArray) {
// 获取查询到的文档唯一ID,这个ID是Lucene在创建文档的时候自动分配的。
int docId = temp.doc;
// 通过文档ID读取文档
Document document = indexSearcher.doc(docId);
System.out.println("******************************************************************************************************");
System.out.println("id: " + document.get("id"));
System.out.println("name: " + document.get("name"));
System.out.println("price: " + document.get("price"));
}
}
// 10. 关闭流
indexReader.close();
}
****************************************************************************************************************************************************************************
16、Field域的使用
【1】Field类:索引的目的是为了查询。比如商品ID、订单号、身份证号这个不用分词。凡是用来展示的都需要存储。
【2】所以每个属性都要进行是否分词、是否索引、是否存储的改造。
// 2.创建文档对象
for (Product temp : productList) {
// 创建文档
Document document = new Document();
// 创建域对象,并且放到文档对象中
document.add(new TextField("id", temp.getId() + "", Field.Store.YES));// 否 是 是(是为了查数据库)
document.add(new TextField("name", temp.getName(), Field.Store.YES));// 是 是 是(因为页面需要展示商品名称)
document.add(new IntPoint("price", temp.getPrice()));// 是(底层的逻辑) 是(根据范围查询) 是
document.add(new StoredField("price", temp.getPrice()));// 与上面组合来完成存储
document.add(new TextField("num", temp.getNum() + "", Field.Store.YES));
document.add(new TextField("image", temp.getImage() + "", Field.Store.YES));
document.add(new TextField("brandName", temp.getBrandName() + "", Field.Store.YES)); // 否 是 是
document.add(new TextField("spec", temp.getSpec() + "", Field.Store.YES));
// 放到文档集合
documentList.add(document);
}
****************************************************************************************************************************************************************************
17、索引库维护
【1】如果数据库数据变了,索引库里怎么同步?
package com.day;
import com.day.pojo.Product;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
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.store.Directory;
import org.apache.lucene.store.FSDirectory;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
// 主函数入口
public class DayApplication {
public static void main(String[] args) throws Exception {
//createIndex();
//searchIndex();
//updateIndex();
deleteIndex();
}
// 一、创建索引
public static void createIndex() throws Exception {
// 1.采集数据
Product product_1 = new Product(1, "华为手机", 3000, 10, "华为.jpg", "华为", "300*300", 5);
Product product_2 = new Product(2, "苹果手机", 8000, 30, "苹果.jpg", "苹果", "500*500", 15);
List<Product> productList = new ArrayList<>();
productList.add(product_1);
productList.add(product_2);
List<Document> documentList = new ArrayList<>(); // 文档集合
// 2.创建文档对象
for (Product temp : productList) {
// 创建文档
Document document = new Document();
// 创建域对象,并且放到文档对象中
document.add(new TextField("id", temp.getId() + "", Field.Store.YES));// 否 是 是(是为了查数据库)
document.add(new TextField("name", temp.getName(), Field.Store.YES));// 是 是 是(因为页面需要展示商品名称)
document.add(new IntPoint("price", temp.getPrice()));// 是(底层的逻辑) 是(根据范围查询) 是
document.add(new StoredField("price", temp.getPrice()));// 与上面组合来完成存储
document.add(new TextField("num", temp.getNum() + "", Field.Store.YES));
document.add(new TextField("image", temp.getImage() + "", Field.Store.YES));
document.add(new StringField("brandName", temp.getBrandName() + "", Field.Store.YES)); // 否 是 是
document.add(new TextField("spec", temp.getSpec() + "", Field.Store.YES));
// 放到文档集合
documentList.add(document);
}
// 3.创建分词器
Analyzer analyzer = new StandardAnalyzer();
// 4.创建index目录对象,目录对象表示索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建IndexWriterConfig对象, 这个对象指定切分词使用的分词器
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
// 6.创建IndexWriter输出流对象,指定输出位置和使用的config初始化对象。
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 7.写入文档到索引库
for (Document temp : documentList) {
indexWriter.addDocument(temp);
}
// 8.释放资源
indexWriter.close();
}
// 二、搜索
public static void searchIndex() throws Exception {
// 1.创建分词器(对搜索的内容进行分词使用)。如华为手机可能拆分为 华为 手机
Analyzer analyzer = new StandardAnalyzer();
// 注意!!!:分词器要和创建索引的时候使用的分词器一模一样(不然搜索的时候就有问题)
// 2.创建查询对象 // 第一个arg默认查询域 //
QueryParser queryParser = new QueryParser("name", analyzer);
// 3.设置搜索关键词
Query query = queryParser.parse("华为"); // queryParser.parse("id:华为手机") 指定从id查,不指定就从默认的name域查
// 4.设置Directory目录对象,指定索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建输入流对象
IndexReader indexReader = DirectoryReader.open(directory);
// 6.创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
// 7.搜索并返回结果
TopDocs topDocs_10 = indexSearcher.search(query, 10);
// 8.获取结果集
ScoreDoc[] scoreDocArray = topDocs_10.scoreDocs;
// 9.遍历结果集
System.out.println("共查询到 " + scoreDocArray.length + " 条数据");
if (scoreDocArray != null) {
for (ScoreDoc temp : scoreDocArray) {
// 获取查询到的文档唯一ID,这个ID是Lucene在创建文档的时候自动分配的。
int docId = temp.doc;
// 通过文档ID读取文档
Document document = indexSearcher.doc(docId);
System.out.println("******************************************************************************************************");
System.out.println("id: " + document.get("id"));
System.out.println("name: " + document.get("name"));
System.out.println("price: " + document.get("price"));
}
}
// 10. 关闭流
indexReader.close();
}
// 三、修改索引
public static void updateIndex() throws Exception {
// 1.需要变更成的内容
Document document = new Document();
// 创建域对象,并且放到文档对象中
document.add(new TextField("id", "110161", Field.Store.YES));// 否 是 是(是为了查数据库)
document.add(new TextField("name", "魅族手机", Field.Store.YES));// 是 是 是(因为页面需要展示商品名称)
document.add(new IntPoint("price", 1000));// 是(底层的逻辑) 是(根据范围查询) 是
document.add(new StoredField("price", 1000));// 与上面组合来完成存储
// 3.创建分词器
Analyzer analyzer = new StandardAnalyzer();
// 4.创建index目录对象,目录对象表示索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建IndexWriterConfig对象, 这个对象指定切分词使用的分词器
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
// 6.创建IndexWriter输出流对象,指定输出位置和使用的config初始化对象。
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 7.修改文档
indexWriter.updateDocument(new Term("id", "1"), document);
// 8.释放资源
indexWriter.close();
}
// 四、删除索引,慎用(根据条件删除)
public static void deleteIndex() throws Exception {
// 3.创建分词器
Analyzer analyzer = new StandardAnalyzer();
// 4.创建index目录对象,目录对象表示索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建IndexWriterConfig对象, 这个对象指定切分词使用的分词器
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
// 6.创建IndexWriter输出流对象,指定输出位置和使用的config初始化对象。
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 7.修改文档
indexWriter.deleteDocuments(new Term("id", "110161"));
//indexWriter.deleteAll(); // 删除所有
// 8.释放资源
indexWriter.close();
}
}
****************************************************************************************************************************************************************************
18、分词器
【1】搜索内容会用分词器再次切分。
【2】去掉停用词(的、是、a、an、the等)。大写转小写。
【3】分词器分为自带的和三方的分词器。
【4】比如:安徽合肥。默认分词器会分为 安 徽 合 肥。注意:分词时用的Analyzer,和搜索使用的一定要是同样的。
****************************************************************************************************************************************************************************
19、原生分词器
【1】StandardAnalyzer 对英文效果好。但是对中文就不行了,是按照字分词的。
****************************************************************************************************************************************************************************
20、空格分词器、SimpleAnalyzer
【1】根据空格分,这种对提前定义好支持的很好。仅仅去掉了空格。
【2】SimpleAnalyzer同样不支持中文分词。
****************************************************************************************************************************************************************************
23、第三方中文分词器
【1】使用中文分词器IKAnalyzer。扩展词典 指定的专有名字。停用词典 凡事出现在停用的都会被过滤掉。
<!--三方分词器****************************************************************************************************************************************************-->
<!-- https://mvnrepository.com/artifact/com.github.magese/ik-analyzer -->
<dependency>
<groupId>com.github.magese</groupId>
<artifactId>ik-analyzer</artifactId>
<version>8.1.0</version>
</dependency>
****************************************************************************************************************************************************************************
24、高级查询(文本查询)
【1】文本搜索、范围搜索、组合搜索。
// 三、数据范围
public static void rangeIndex() throws Exception {
// 1.创建分词器(对搜索的内容进行分词使用)。如华为手机可能拆分为 华为 手机
Analyzer analyzer = new IKAnalyzer();
// 注意!!!:分词器要和创建索引的时候使用的分词器一模一样(不然搜索的时候就有问题)
// 2.创建查询对象 // 第一个arg默认查询域 //
Query query = IntPoint.newRangeQuery("price", 2500, 7999);
// 4.设置Directory目录对象,指定索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建输入流对象
IndexReader indexReader = DirectoryReader.open(directory);
// 6.创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
// 7.搜索并返回结果
TopDocs topDocs_10 = indexSearcher.search(query, 10);
// 8.获取结果集
ScoreDoc[] scoreDocArray = topDocs_10.scoreDocs;
// 9.遍历结果集
System.out.println("共查询到 " + scoreDocArray.length + " 条数据");
if (scoreDocArray != null) {
for (ScoreDoc temp : scoreDocArray) {
// 获取查询到的文档唯一ID,这个ID是Lucene在创建文档的时候自动分配的。
int docId = temp.doc;
// 通过文档ID读取文档
Document document = indexSearcher.doc(docId);
System.out.println("******************************************************************************************************");
System.out.println("id: " + document.get("id"));
System.out.println("name: " + document.get("name"));
System.out.println("price: " + document.get("price"));
}
}
// 10. 关闭流
indexReader.close();
}
【2】组合查询
// 四、组合查询
public static void togetherIndex() throws Exception {
// 1.创建分词器(对搜索的内容进行分词使用)。如华为手机可能拆分为 华为 手机
Analyzer analyzer = new IKAnalyzer();
// 注意!!!:分词器要和创建索引的时候使用的分词器一模一样(不然搜索的时候就有问题)
// 2.创建查询对象 // 第一个arg默认查询域 //
Query query_1 = IntPoint.newRangeQuery("price", 2500, 17999);
QueryParser queryParser = new QueryParser("name", analyzer);
// 3.设置搜索关键词 queryParser.parse("华为 AND 手机")
Query query_2 = queryParser.parse("苹果 OR 手机"); // queryParser.parse("id:华为手机") 指定从id查,不指定就从默认的name域查
BooleanQuery.Builder builderQuery = new BooleanQuery.Builder();
builderQuery.add(query_1, BooleanClause.Occur.MUST); // MUST = and SHOULD= or
builderQuery.add(query_2, BooleanClause.Occur.MUST);
// 4.设置Directory目录对象,指定索引库的位置
Directory directory = FSDirectory.open(Paths.get("src/main/resources/index"));
// 5.创建输入流对象
IndexReader indexReader = DirectoryReader.open(directory);
// 6.创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
// 7.搜索并返回结果
TopDocs topDocs_10 = indexSearcher.search(builderQuery.build(), 10);
// 8.获取结果集
ScoreDoc[] scoreDocArray = topDocs_10.scoreDocs;
// 9.遍历结果集
System.out.println("共查询到 " + scoreDocArray.length + " 条数据");
if (scoreDocArray != null) {
for (ScoreDoc temp : scoreDocArray) {
// 获取查询到的文档唯一ID,这个ID是Lucene在创建文档的时候自动分配的。
int docId = temp.doc;
// 通过文档ID读取文档
Document document = indexSearcher.doc(docId);
System.out.println("******************************************************************************************************");
System.out.println("id: " + document.get("id"));
System.out.println("name: " + document.get("name"));
System.out.println("price: " + document.get("price"));
}
}
// 10. 关闭流
indexReader.close();
}
****************************************************************************************************************************************************************************
26、实际案例
【1】商城页面。需求:商品名称,价格筛选等
【2】关闭springboot缓存spring.thymeleaf.cache=false
【3】不得不说thymeleaf真没有VUE好用,卧槽。
****************************************************************************************************************************************************************************
36、相关度排序
【1】词的权重。Term Frequency 出现的次数、 Document Frequency 出现的文档个数