Lucene轻量级搜索引擎,真的太强了!!!Solr 和 ES 都是基于它

这篇具有很好参考价值的文章主要介绍了Lucene轻量级搜索引擎,真的太强了!!!Solr 和 ES 都是基于它。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、基础知识

1、Lucene 是什么

Lucene 是一个本地全文搜索引擎,Solr 和 ElasticSearch 都是基于 Lucene 的封装

Lucene 适合那种轻量级的全文搜索,我就是服务器资源不够,如果上 ES 的话会很占用服务器资源,所有就选择了 Lucene 搜索引擎

2、倒排索引原理

全文搜索的原理是使用了倒排索引,那么什么是倒排索引呢?

  1. 先通过中文分词器,将文档中包含的关键字全部提取出来,比如我爱中国,会通过分词器分成我,爱,中国,然后分别对应‘我爱中国’
  2. 然后再将关键字与文档的对应关系保存起来
  3. 最后对关键字本身做索引排序

3、与传统数据库对比

Lucene DB
数据库表(table) 索引(index)
行(row) 文档(document)
列(column) 字段(field)

4、数据类型

常见的字段类型

  1. StringField:这是一个不可分词的字符串字段类型,适用于精确匹配和排序
  2. TextField:这是一个可分词的字符串字段类型,适用于全文搜索和模糊匹配
  3. IntField、LongField、FloatField、DoubleField:这些是数值字段类型,用于存储整数和浮点数。
  4. DateField:这是一个日期字段类型,用于存储日期和时间。
  5. BinaryField:这是一个二进制字段类型,用于存储二进制数据,如图片、文件等。
  6. StoredField:这是一个存储字段类型,用于存储不需要被索引的原始数据,如文档的内容或其他附加信息。

Lucene 分词器是将文本内容分解成单独的词汇(term)的工具。Lucene 提供了多种分词器,其中一些常见的包括

  1. StandardAnalyzer:这是 Lucene 默认的分词器,它使用 UnicodeText 解析器将文本转换为小写字母,并且根据空格、标点符号和其他字符来进行分词。
  2. CJKAnalyzer:这个分词器专门为中日韩语言设计,它可以正确地处理中文、日文和韩文的分词。
  3. KeywordAnalyzer:这是一个不分词的分词器,它将输入的文本作为一个整体来处理,常用于处理精确匹配的情况。
  4. SimpleAnalyzer:这是一个非常简单的分词器,它仅仅按照非字母字符将文本分割成小写词汇。
  5. WhitespaceAnalyzer:这个分词器根据空格将文本分割成小写词汇,不会进行任何其他的处理。

但是对于中文分词器,我们一般常用第三方分词器IKAnalyzer,需要引入它的POM文件

二、最佳实践

1、依赖导入

<lucene.version>8.1.1</lucene.version>
<IKAnalyzer-lucene.version>8.0.0</IKAnalyzer-lucene.version>

<!--============lucene start================-->
<!-- Lucene核心库 -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>${lucene.version}</version>
</dependency>

<!-- Lucene的查询解析器 -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-queryparser</artifactId>
    <version>${lucene.version}</version>
</dependency>

<!-- Lucene的默认分词器库 -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-common</artifactId>
    <version>${lucene.version}</version>
</dependency>

<!-- Lucene的高亮显示 -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-highlighter</artifactId>
    <version>${lucene.version}</version>
</dependency>

<!-- ik分词器 -->
<dependency>
    <groupId>com.jianggujin</groupId>
    <artifactId>IKAnalyzer-lucene</artifactId>
    <version>${IKAnalyzer-lucene.version}</version>
</dependency>
<!--============lucene end================-->

2、创建索引

  1. 先制定索引的基本数据,包括索引名称和字段
/**
 * @author: sunhhw
 * @date: 2023/12/25 17:39
 * @description: 定义文章文档字段和索引名称
 */

public interface IArticleIndex {

    /**
     * 索引名称
     */

    String INDEX_NAME = "article";

    // --------------------- 文档字段 ---------------------
    String COLUMN_ID = "id";
    String COLUMN_ARTICLE_NAME = "articleName";
    String COLUMN_COVER = "cover";
    String COLUMN_SUMMARY = "summary";
    String COLUMN_CONTENT = "content";
    String COLUMN_CREATE_TIME = "createTime";
}
  1. 创建索引并新增文档
/**
 * 创建索引并设置数据
 *
 * @param indexName 索引地址
 */

public void addDocument(String indexName, List<Document> documentList) {
    // 配置索引的位置 例如:indexDir = /app/blog/index/article
    String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
    try {
        File file = new File(indexDir);
        // 若不存在,则创建目录
        if (!file.exists()) {
            FileUtils.forceMkdir(file);
        }
        // 读取索引目录
        Directory directory = FSDirectory.open(Paths.get(indexDir));
        // 中文分析器
        Analyzer analyzer = new IKAnalyzer();
        // 索引写出工具的配置对象
        IndexWriterConfig conf = new IndexWriterConfig(analyzer);
        // 创建索引
        IndexWriter indexWriter = new IndexWriter(directory, conf);
        long count = indexWriter.addDocuments(documentList);
        log.info("[批量添加索引库]总数量:{}", documentList.size());
        // 提交记录
        indexWriter.commit();
        // 关闭close
        indexWriter.close();
    } catch (Exception e) {
        log.error("[创建索引失败]indexDir:{}", indexDir, e);
        throw new UtilsException("创建索引失败", e);
    }
}
  1. 注意这里有个坑,就是这个indexWriter.close();必须要关闭, 不然在执行其他操作的时候会有一个write.lock文件锁控制导致操作失败
  2. indexWriter.addDocuments(documentList)这是批量添加,单个添加可以使用indexWriter.addDocument()
  1. 单元测试
@Test
public void create_index_test() {
    ArticlePO articlePO = new ArticlePO();
    articlePO.setArticleName("git的基本使用" + i);
    articlePO.setContent("这里是git的基本是用的内容" + i);
    articlePO.setSummary("测试摘要" + i);
    articlePO.setId(String.valueOf(i));
    articlePO.setCreateTime(LocalDateTime.now());
    Document document = buildDocument(articlePO);
    LuceneUtils.X.addDocument(IArticleIndex.INDEX_NAME, document);
}

private Document buildDocument(ArticlePO articlePO) {
    Document document = new Document();
    LocalDateTime createTime = articlePO.getCreateTime();
    String format = LocalDateTimeUtil.format(createTime, DateTimeFormatter.ISO_LOCAL_DATE);

    // 因为ID不需要分词,使用StringField字段
    document.add(new StringField(IArticleIndex.COLUMN_ID, articlePO.getId() == null ? "" : articlePO.getId(), Field.Store.YES));
    // 文章标题articleName需要搜索,所以要分词保存
    document.add(new TextField(IArticleIndex.COLUMN_ARTICLE_NAME, articlePO.getArticleName() == null ? "" : articlePO.getArticleName(), Field.Store.YES));
    // 文章摘要summary需要搜索,所以要分词保存
    document.add(new TextField(IArticleIndex.COLUMN_SUMMARY, articlePO.getSummary() == null ? "" : articlePO.getSummary(), Field.Store.YES));
     // 文章内容content需要搜索,所以要分词保存
    document.add(new TextField(IArticleIndex.COLUMN_CONTENT, articlePO.getContent() == null ? "" : articlePO.getContent(), Field.Store.YES));
    // 文章封面不需要分词,但是需要被搜索出来展示
    document.add(new StoredField(IArticleIndex.COLUMN_COVER, articlePO.getCover() == null ? "" : articlePO.getCover()));
    // 创建时间不需要分词,仅需要展示
    document.add(new StringField(IArticleIndex.COLUMN_CREATE_TIME, format, Field.Store.YES));
    return document;
}

3、更新文档

  1. 更新索引方法
/**
 * 更新文档
 *
 * @param indexName 索引地址
 * @param document  文档
 * @param condition 更新条件
 */

public void updateDocument(String indexName, Document document, Term condition) {
    String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
    try {
        // 读取索引目录
        Directory directory = FSDirectory.open(Paths.get(indexDir));
        // 中文分析器
        Analyzer analyzer = new IKAnalyzer();
        // 索引写出工具的配置对象
        IndexWriterConfig conf = new IndexWriterConfig(analyzer);
        // 创建索引
        IndexWriter indexWriter = new IndexWriter(directory, conf);
        indexWriter.updateDocument(condition, document);
        indexWriter.commit();
        indexWriter.close();
    } catch (Exception e) {
        log.error("[更新文档失败]indexDir:{},document:{},condition:{}", indexDir, document, condition, e);
        throw new ServiceException();
    }
}
  1. 单元测试
@Test
public void update_document_test() {
    ArticlePO articlePO = new ArticlePO();
    articlePO.setArticleName("git的基本使用=编辑");
    articlePO.setContent("这里是git的基本是用的内容=编辑");
    articlePO.setSummary("测试摘要=编辑");
    articlePO.setId("2");
    articlePO.setCreateTime(LocalDateTime.now());
    Document document = buildDocument(articlePO);
    LuceneUtils.X.updateDocument(IArticleIndex.INDEX_NAME, document, new Term("id""2"));
}
  1. 更新的时候,如果存在就更新那条记录,如果不存在就会新增一条记录
  2. new Term("id", "2")搜索条件,跟数据库里的where id = 2差不多
  3. IArticleIndex.INDEX_NAME = article 索引名称

4、删除文档

  1. 删除文档方法
/**
* 删除文档
*
@param indexName 索引名称
@param condition 更新条件
*/

public void deleteDocument(String indexName, Term condition) {
  String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  try {
      // 读取索引目录
      Directory directory = FSDirectory.open(Paths.get(indexDir));
      // 索引写出工具的配置对象
      IndexWriterConfig conf = new IndexWriterConfig();
      // 创建索引
      IndexWriter indexWriter = new IndexWriter(directory, conf);

      indexWriter.deleteDocuments(condition);
      indexWriter.commit();
      indexWriter.close();
  } catch (Exception e) {
      log.error("[删除文档失败]indexDir:{},condition:{}", indexDir, condition, e);
      throw new ServiceException();
  }
}
  1. 单元测试
@Test
public void delete_document_test() {
    LuceneUtils.X.deleteDocument(IArticleIndex.INDEX_NAME, new Term(IArticleIndex.COLUMN_ID, "1"));
}
  1. 删除文档跟编辑文档类似

5、删除索引

把改索引下的数据全部清空

/**
* 删除索引
*
@param indexName 索引地址
*/

public void deleteIndex(String indexName) {
  String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  try {
      // 读取索引目录
      Directory directory = FSDirectory.open(Paths.get(indexDir));
      // 索引写出工具的配置对象
      IndexWriterConfig conf = new IndexWriterConfig();
      // 创建索引
      IndexWriter indexWriter = new IndexWriter(directory, conf);
      indexWriter.deleteAll();
      indexWriter.commit();
      indexWriter.close();
  } catch (Exception e) {
      log.error("[删除索引失败]indexDir:{}", indexDir, e);
      throw new ServiceException();
  }
}

6、普通查询

  1. TermQuery查询
Term term = new Term("title""lucene");
Query query = new TermQuery(term);

上述代码表示通过精确匹配字段"title"中包含"lucene"的文档。

  1. PhraseQuery查询
PhraseQuery.Builder builder = new PhraseQuery.Builder();
builder.add(new Term("content""open"));
builder.add(new Term("content""source"));
PhraseQuery query = builder.build();

上述代码表示在字段"content"中查找包含"open source"短语的文档

  1. BooleanQuery查询
TermQuery query1 = new TermQuery(new Term("title""lucene"));
TermQuery query2 = new TermQuery(new Term("author""john"));
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(query1, BooleanClause.Occur.MUST);
builder.add(query2, BooleanClause.Occur.MUST);
BooleanQuery query = builder.build();

上述代码表示使用布尔查询同时满足"title"字段包含"lucene"和"author"字段包含"john"的文档。

  1. WildcardQuery查询
WildcardQuery示例:
java
WildcardQuery query = new WildcardQuery(new Term("title""lu*n?e"));

上述代码表示使用通配符查询匹配"title"字段中以"lu"开头,且第三个字符为任意字母,最后一个字符为"e"的词项

  1. MultiFieldQueryParser查询
String[] fields = {"title""content""author"};
Analyzer analyzer = new StandardAnalyzer();

MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer);
Query query = parser.parse("lucene search");

a. 在"title", "content", "author"三个字段中搜索关键字"lucene search"的文本数据 b. MultiFieldQueryParser 默认使用 OR 运算符将多个字段的查询结果合并,即只要在任意一个字段中匹配成功即

可以使用MultiFieldQueryParser查询来封装一个简单的搜索工具类,这个较为常用

/**
* 关键词搜索
*
@param indexName 索引目录
@param keyword   查询关键词
@param columns   被搜索的字段
@param current   当前页
@param size      每页数据量
@return
*/

public List<Document> search(String indexName, String keyword, String[] columns, int current, int size) {
  String indexDir = luceneProperties.getIndexDir() + File.separator + indexName;
  try {
      // 打开索引目录
      Directory directory = FSDirectory.open(Paths.get(indexDir));
      IndexReader reader = DirectoryReader.open(directory);
      IndexSearcher searcher = new IndexSearcher(reader);
      // 中文分析器
      Analyzer analyzer = new IKAnalyzer();
      // 查询解析器
      QueryParser parser = new MultiFieldQueryParser(columns, analyzer);
      // 解析查询关键字
      Query query = parser.parse(keyword);
      // 执行搜索,获取匹配查询的前 limit 条结果。
      int limit = current * size;
      // 搜索前 limit 条结果
      TopDocs topDocs = searcher.search(query, limit); 
      // 匹配的文档数组
      ScoreDoc[] scoreDocs = topDocs.scoreDocs;
      // 计算分页的起始 - 结束位置
      int start = (current - 1) * size;
      int end = Math.min(start + size, scoreDocs.length);
      // 返回指定页码的文档
      List<Document> documents = new ArrayList<>();
      for (int i = start; i < end; i++) {
          Document doc = searcher.doc(scoreDocs[i].doc);
          documents.add(doc);
      }
      // 释放资源
      reader.close();
      return documents;
  } catch (Exception e) {
      log.error("查询 Lucene 错误: ", e);
      return null;
  }
}

7、关键字高亮

@Test
public void searchArticle() throws InvalidTokenOffsetsException, IOException, ParseException {
    String keyword = "安装";
    String[] fields = {IArticleIndex.COLUMN_CONTENT, IArticleIndex.COLUMN_ARTICLE_NAME};
    // 先查询出文档列表
    List<Document> documentList = LuceneUtils.X.search(IArticleIndex.INDEX_NAME, keyword, fields, 1100);

    // 中文分词器
    Analyzer analyzer = new IKAnalyzer();
    // 搜索条件
    QueryParser queryParser = new MultiFieldQueryParser(fields, analyzer);
    // 搜索关键词,也就是需要高亮的字段
    Query query = queryParser.parse(keyword);
    // 高亮html语句
    Formatter formatter = new SimpleHTMLFormatter("<span style=\"color: #f73131\">""</span>");
    QueryScorer scorer = new QueryScorer(query);
    Highlighter highlighter = new Highlighter(formatter, scorer);
    // 设置片段长度,一共展示的长度
    highlighter.setTextFragmenter(new SimpleFragmenter(50));
    List<SearchArticleVO> list = new ArrayList<>();

    for (Document doc : documentList) {
        SearchArticleVO articleVO = new SearchArticleVO();
        articleVO.setId(doc.get(IArticleIndex.COLUMN_ID));
        articleVO.setCover(doc.get(IArticleIndex.COLUMN_COVER));
        articleVO.setArticleName(doc.get(IArticleIndex.COLUMN_ARTICLE_NAME));
        articleVO.setSummary(doc.get(IArticleIndex.COLUMN_SUMMARY));
        articleVO.setCreateTime(LocalDate.parse(doc.get(IArticleIndex.COLUMN_CREATE_TIME)));
        for (String field : fields) {
            // 为文档生成高亮
            String text = doc.get(field);
            // 使用指定的分析器对文本进行分词
            TokenStream tokenStream = TokenSources.getTokenStream(field, text, analyzer);
            // 找到其中一个关键字就行了
            String bestFragment = highlighter.getBestFragment(tokenStream, text);
            if (StringUtils.isNotBlank(bestFragment)) {
                // 输出高亮结果,取第一条即可
                if (field.equals(IArticleIndex.COLUMN_ARTICLE_NAME)) {
                    articleVO.setArticleName(bestFragment);
                }
                if (field.equals(IArticleIndex.COLUMN_CONTENT)) {
                    articleVO.setSummary(bestFragment);
                }
            }
        }
        list.add(articleVO);
    }
}

我是一零贰肆,一个关注Java技术和记录生活的博主。

欢迎扫码关注“一零贰肆”的公众号,一起学习,共同进步,多看路,少踩坑。

Lucene轻量级搜索引擎,真的太强了!!!Solr 和 ES 都是基于它文章来源地址https://www.toymoban.com/news/detail-838698.html

到了这里,关于Lucene轻量级搜索引擎,真的太强了!!!Solr 和 ES 都是基于它的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【表达式引擎】简单高效的轻量级Java表达式引擎:Aviator

    Aviator 是一个高性能、、轻量级的表达式引擎,支持表达式动态求值。其设计目标为轻量级和高性能,相比于 Groovy 和 JRuby 的笨重, Aviator 就显得更加的小巧。与其他的轻量级表达式引擎不同,其他的轻量级表达式引擎基本都是通过解释代码的方式来运行,而 Aviator 则是直接

    2024年02月09日
    浏览(41)
  • Orillusion引擎正式开源!AIGC时代下的WebGPU轻量级3D渲染引擎!

    开源生态的建设根植于社区,开发者在社区共享、共创、共赢,将会激发出无限的创造力,这就是开源最大的魅力! 选择开源,源于我们坚信,“独行快,众行远”! WebGPU经过六年的时间,终于在2023年4月6日,由Chrome团队发布。5月2号,在Chrome113版本上,WebGPU被默认启动。

    2024年02月11日
    浏览(32)
  • 教你使用PHP实现一个轻量级HTML模板引擎

    🏆作者简介,黑夜开发者,全栈领域新星创作者✌,2023年6月csdn上海赛道top4。多年电商行业从业经验,对系统架构,数据分析处理等大规模应用场景有丰富经验。 🏆本文已收录于PHP专栏:PHP进阶实战教程。 🏆另有专栏PHP入门基础教程,希望各位大佬多多支持❤️。 在 W

    2024年02月15日
    浏览(29)
  • Spring Boot整合Postgres实现轻量级全文搜索

    有这样一个带有搜索功能的用户界面需求: 搜索流程如下所示: 这个需求涉及两个实体: “评分(Rating)、用户名(Username)”数据与 User 实体相关 “创建日期(create date)、观看次数(number of views)、标题(title)、正文(body)”与 Story 实体相关 需要支持的功能对 User

    2024年02月19日
    浏览(29)
  • 【java表达式引擎】四、高性能、轻量级的AviatorScript

    github:(https://github.com/killme2008/aviatorscript%60) 参考文档1:https://www.yuque.com/boyan-avfmj/aviatorscript 参考博客2:https://blog.csdn.net/ZhangQingmu/article/details/125087255 Aviator起源于2011年,由国内的开发者开源的,表达式引擎 表达式引擎当时国内开源的已经有 IKExpression,可惜是纯解释执行的,

    2024年02月10日
    浏览(40)
  • 告别if else!试试这款轻量级流程引擎吧,跟SpringBoot绝配!

    之前同事用了一款轻量级的规则引擎脚本 AviatorScript ,我也跟着用了起来,真的挺香,能少写很多代码。这期就给大家介绍一下这款规则引擎。 AviatorScript 是一门高性能、轻量级寄宿于 JVM (包括 Android 平台)之上的脚本语言。 它起源于2010年,作者对当时已有的一些产品不是

    2024年02月13日
    浏览(34)
  • SimSearch:一个轻量级的springboot项目索引构建工具,实现快速模糊搜索

    大部分项目都会涉及模糊搜索功能,而实现模糊搜索一般分为两个派系: like简约派系 搜索引擎派系 对于较为大型的项目来说,使用Solr、ES或者Milvus之类的引擎是比较流行的选择了(效果只能说优秀),而对于中小型项目,如果考虑这些较为重型的引擎,就意味着开发成本和

    2024年02月02日
    浏览(77)
  • DP读书:社区文档(小白向)解读——iSulad 轻量级容器引擎功能介绍以及代码架构解析

    容器技术方案) 每天逛这openEuler的社区和社群,总是看到iSulad,今天啃已啃这个项目的入门玩法: lifeng2221dd1 2020-09-14 作者简介:李峰, 具有多年容器、操作系统软件开发经验,对容器引擎、runtime 等领域有比较深入的研究与理解。深度参与 lxc、containers 等开源容器社区。现在

    2024年02月21日
    浏览(28)
  • ASP.NET基于Ajax+Lucene构建搜索引擎的设计和实现

    3 需求分析 3.1 同步环境 本系统的同步环境如图3: 添加图片注释,不超过 140 字(可选) 功能需求 本设计要实现的功能: 1. 能够对Internet上的网页内容、标题、链接等信息按链式收集。 2. 能够实现一定链接深度的网页收集,也就是在Internet上实现一定的URL级的数据收录。

    2024年02月03日
    浏览(29)
  • Lucene和Solr和Elasticsearch区别,全文检索引擎工具包Lucene索引流程和搜索流程实操

    我们生活中的数据总体分为两种: 结构化数据和非结构化数据 。 结构化数据 :指具有固定格式或有限长度的数据,如数据库,元数据等。 非结构化数据 :指不定长或无固定格式的数据,如 互联网数据、邮件,word文档等。 非结构化数据又有一种叫法叫全文数据 按照数据的

    2024年02月03日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包