*Lucene**是一套用于全文检索和搜索的开放源码程序库

优采云 发布时间: 2021-07-27 18:56

  *Lucene**是一套用于全文检索和搜索的开放源码程序库

  每天早上7:30准时送干货

  大家好,我是鸭血粉丝,大家都叫我粉丝,搜索引擎一定不能默认给大家,我们项目中经常用到的ElasticSearch是搜索引擎,一定不能用在我们的日志系统比较少,ELK作为一个整体,基本上是运维的标准配置。另外,目前搜索引擎底层是基于Lucene的。

  阿凡最近遇到一个需求,因为数据量还没有达到使用ElasticSearch所需的水平,又不想单独部署集群,所以打算实现一个简单的基于Lucene的搜索服务。一起来看看吧。

  背景

  **Lucene ** 是一个用于全文检索和搜索的开源库,由 Apache 软件基金会支持和提供。 Lucene 提供了一个简单但功能强大的应用程序接口,可以进行全文索引和搜索。 Lucene 是目前最流行的免费 Java 信息​​检索库。

  以上解释来自维基百科。我们只需要知道Lucene可以进行全文索引和搜索。这里的索引是一个动词,意思是我们可以索引和记录文档或文章或文件等数据。索引后,我们的查询会很快。

  有时index这个词是动词,意思是我们要索引数据,有时它是名词。我们需要根据上下文来判断。新华字典前面的字母表或书前面的目录本质上都是索引。

  访问引入依赖

  首先我们创建一个SpringBoot项目,然后在pom文件中添加如下内容。我这里使用的lucene版本是7.2.1,

  

    7.2.1

 org.apache.lucene

 lucene-core

 ${lucene.version}

 org.apache.lucene

 lucene-queryparser

 ${lucene.version}

 org.apache.lucene

 lucene-analyzers-common

 ${lucene.version}

  索引数据

  在使用Lucene之前,我们需要对一些文件进行索引,然后通过关键词进行查询。让我们模拟整个过程。这里为了方便一些数据的模拟,正常的数据应该是从数据库或者文件中加载的。我们的想法是这样的:

  生成多条实体数据;将实体数据映射为 Lucene 文档格式;索引文件;根据关键词查询文档;

  第一步是创建一个实体,如下所示:

  import lombok.Data;

@Data

public class ArticleModel {

    private String title;

    private String author;

    private String content;

}

  我们再写一个工具类来索引数据,代码如下:

  import org.apache.commons.collections.CollectionUtils;

import org.apache.commons.lang.StringUtils;

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.standard.StandardAnalyzer;

import org.apache.lucene.document.*;

import org.apache.lucene.index.IndexWriter;

import org.apache.lucene.index.IndexWriterConfig;

import org.apache.lucene.store.Directory;

import org.apache.lucene.store.FSDirectory;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Component;

import java.io.IOException;

import java.nio.file.Paths;

import java.util.ArrayList;

import java.util.List;

import java.util.Map;

public class LuceneIndexUtil {

    private static String INDEX_PATH = "/opt/lucene/demo";

    private static IndexWriter writer;

    public static LuceneIndexUtil getInstance() {

        return SingletonHolder.luceneUtil;

    }

    private static class SingletonHolder {

        public final static LuceneIndexUtil luceneUtil = new LuceneIndexUtil();

    }

    private LuceneIndexUtil() {

        this.initLuceneUtil();

    }

    private void initLuceneUtil() {

        try {

            Directory dir = FSDirectory.open(Paths.get(INDEX_PATH));

            Analyzer analyzer = new StandardAnalyzer();

            IndexWriterConfig iwc = new IndexWriterConfig(analyzer);

            writer = new IndexWriter(dir, iwc);

        } catch (IOException e) {

            log.error("create luceneUtil error");

            if (null != writer) {

                try {

                    writer.close();

                } catch (IOException ioException) {

                    ioException.printStackTrace();

                } finally {

                    writer = null;

                }

            }

        }

    }

    /**

     * 索引单个文档

     *

     * @param doc 文档信息

     * @throws IOException IO 异常

     */

    public void addDoc(Document doc) throws IOException {

        if (null != doc) {

            writer.addDocument(doc);

            writer.commit();

            writer.close();

        }

    }

    /**

     * 索引单个实体

     *

     * @param model 单个实体

     * @throws IOException IO 异常

     */

    public void addModelDoc(Object model) throws IOException {

        Document document = new Document();

        List fields = luceneField(model.getClass());

        fields.forEach(document::add);

        writer.addDocument(document);

        writer.commit();

        writer.close();

    }

    /**

     * 索引实体列表

     *

     * @param objects 实例列表

     * @throws IOException IO 异常

     */

    public void addModelDocs(List objects) throws IOException {

        if (CollectionUtils.isNotEmpty(objects)) {

            List docs = new ArrayList();

            objects.forEach(o -> {

                Document document = new Document();

                List fields = luceneField(o);

                fields.forEach(document::add);

                docs.add(document);

            });

            writer.addDocuments(docs);

        }

    }

    /**

     * 清除所有文档

     *

     * @throws IOException IO 异常

     */

    public void delAllDocs() throws IOException {

        writer.deleteAll();

    }

    /**

     * 索引文档列表

     *

     * @param docs 文档列表

     * @throws IOException IO 异常

     */

    public void addDocs(List docs) throws IOException {

        if (CollectionUtils.isNotEmpty(docs)) {

            long startTime = System.currentTimeMillis();

            writer.addDocuments(docs);

            writer.commit();

            log.info("共索引{}个 Document,共耗时{} 毫秒", docs.size(), (System.currentTimeMillis() - startTime));

        } else {

            log.warn("索引列表为空");

        }

    }

    /**

     * 根据实体 class 对象获取字段类型,进行 lucene Field 字段映射

     *

     * @param modelObj 实体 modelObj 对象

     * @return 字段映射列表

     */

    public List luceneField(Object modelObj) {

        Map classFields = ReflectionUtils.getClassFields(modelObj.getClass());

        Map classFieldsValues = ReflectionUtils.getClassFieldsValues(modelObj);

        List fields = new ArrayList();

        for (String key : classFields.keySet()) {

            Field field;

            String dataType = StringUtils.substringAfterLast(classFields.get(key).toString(), ".");

            switch (dataType) {

                case "Integer":

                    field = new IntPoint(key, (Integer) classFieldsValues.get(key));

                    break;

                case "Long":

                    field = new LongPoint(key, (Long) classFieldsValues.get(key));

                    break;

                case "Float":

                    field = new FloatPoint(key, (Float) classFieldsValues.get(key));

                    break;

                case "Double":

                    field = new DoublePoint(key, (Double) classFieldsValues.get(key));

                    break;

                case "String":

                    String string = (String) classFieldsValues.get(key);

                    if (StringUtils.isNotBlank(string)) {

                        if (string.length()  {

                if (value instanceof String) {

                    Query queryString = new PhraseQuery(key, (String) value);

//                    Query queryString = new TermQuery(new Term(key, (String) value));

                    builder.add(queryString, BooleanClause.Occur.MUST);

                }

            });

            return searcher.search(builder.build(), 10);

        }

        return null;

    }

}

  在demo.java中添加搜索代码如下:

  //查询数据

   Map map = new HashMap();

   map.put("title", "Java 极客技术");

//   map.put("title", "极客技术");

//   map.put("content", "最");

   LuceneSearchUtil searchUtil = LuceneSearchUtil.getInstance();

   TopDocs topDocs = searchUtil.searchByMap(map);

   System.out.println(topDocs.totalHits);

  运行结果如下,说明找到了两个。

  

  通过可视化工具,我们可以看到标题是“Java Geek Technology”,确实有两条记录,我们也确认只插入了两条数据。注意,如果根据其他字符搜索,可能查不到,因为这里阿凡的tokenizer使用的是默认tokenizer,朋友们可以根据自己的情况使用对应的tokenizer。

  

  到目前为止我们可以索引和搜索数据,但这仍然是一个简单的入口操作。对于不同类型的字段,我们需要使用不同的查询方式,并且根据系统的特点,我们需要使用特定的tokenizer,默认标准的tokenizer不一定符合我们的使用场景。而且我们在索引数据的时候,还需要根据字段类型设置不同的Fields。以上案例只是一个演示,不能用于生产。搜索引擎是互联网行业的领导者。许多先进的互联网技术都是从搜索引擎发展而来的。

  相关代码阿凡已经放在GitHub上,回复公众号【源码仓库】即可获取。

  如果你喜欢我们的文章,欢迎转发并点击让更多人看到。我们也欢迎热爱技术和学习的朋友加入我们的知识星球。我们一起成长进步。

  

  令人兴奋的回顾过去的问题

  好孩子,答应一个粉丝,你一定要经常使用这些Linux命令

  阿凡教你用爬虫比对某东商数据

  重点丨什么是双查锁模式?为什么需要 volatile 关键字?

  本文分享自微信公众号-Java极客科技(Javageektech)。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线