Lucene从入门到精通

****************************************************************************************************************************************************************************

复制代码
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>

IKAnalyzer.rar

****************************************************************************************************************************************************************************

复制代码
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 出现的文档个数
相关推荐
ZWZhangYu1 小时前
【MyBatis源码】深入分析TypeHandler原理和源码
数据库·oracle·mybatis
小鸡脚来咯18 小时前
springboot 整合mybatis
java·spring boot·mybatis
种树人2024081919 小时前
MyBatis xml 文件中 SQL 语句的小于号未转义导致报错
mybatis
看山还是山,看水还是。19 小时前
Oracle 外键
运维·数据结构·数据库·笔记·程序人生·oracle·全文检索
码农派大星。21 小时前
MyBatis操作--进阶
mybatis
爱读源码的大都督1 天前
MyBatis中的LanguageDriver的作用是什么
java·spring boot·mybatis
infiniteWei1 天前
【Lucene】架构概览和核心组件介绍
搜索引擎·架构·全文检索·lucene
鹿屿二向箔1 天前
基于SSM(Spring + Spring MVC + MyBatis)框架的快递管理系统
spring·mvc·mybatis
2的n次方_1 天前
MyBatis——增删查改(XML 方式)
xml·数据库·mybatis
bjzhang751 天前
SQLite 全文检索:快速高效的文本查询方案
sqlite·全文检索