当前位置: 首页 > news >正文

lucene 思维导图,让搜索引擎不再难懂

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

image

   (公众号回复“lucene”获取源导图)

今天,我们来讲讲lucene,同学们搬好板凳坐好啦。

(lucene干嘛的呀?)

首先我们来看张思维导图:

image

以上是我们java常用的全文搜索引擎框架,很多项目的搜索功能都是基于以上4个框架完成的。

所以lucene到底是干啥的?

Lucene是一套用于全文检索和搜索的开放源代码程序库,一个能够轻松集添加搜索功能到一个应用程序中的简单却强大的核心代码库和API。

Lucene,目前最受欢迎的Java全文搜索框架。原因很简单,hibernate search、solr、elasticsearch都是基于lucene拓展出来的搜索引擎。

Hibernate Search是在apache Lucene的基础上建立的主要用于Hibernate的持久化模型的全文检索工具。

Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

Solr它是一种开放源码的、基于 Lucene Java 的搜索服务器,易于加入到 Web 应用程序中。提供了层面搜索(就是统计)、命中醒目显示并且支持多种输出格式(包括XML/XSLT 和JSON等格式)。

所以lucene牛不牛逼!!

接下来,我们分为以下几个部分去理解、打开lucene的真面目。

  • 相关概念

  • 构建索引与查询索引过程

  • 倒排索引

  • 可视化工具

  • 项目应用指南

相关概念

lucene官方网站:http://lucene.apache.org/

既然是全文搜索工具,肯定有一定的排序结构和规则。当我们输入关键字的时候,lucene能安装内部的层次结构快速检索出我需要的内容。这里面会涉及到几个层次和概念。

image

索引库(Index)

一个目录一个索引库,同一文件夹中的所有的文件构成一个Lucene索引库。类似数据库的表的概念。

image

(lucene的索引实例)

段(Segment)

Lucene索引可能由多个子索引组成,这些子索引成为段。每一段都是完整独立的索引,能被搜索。

文档(Document)

一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可以合并。段是索引数据存储的单元。类似数据库内的行**或者文档数据库内的文档**的概念。

域(Field)

一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等。类似于数据库表中的字段**。

词(Term)

词是索引的最小单位,是经过词法分析和语言处理后的字符串。一个Field由一个或多个Term组成。比如标题内容是“hello lucene”,经过分词之后就是“hello”,“lucene”,这两个单词就是Term的内容信息,当关键字搜索“hello”或者“lucene”的时候这个标题就会被搜索出来。

**分词器(**Analyzer)

一段有意义的文字需要通过Analyzer来分割成一个个词语后才能按关键词搜索。StandartdAnalyzer是Lucene中常用的分析器,中文分词有CJKAnalyzer、SmartChinieseAnalyzer等。

image

(lucene 索引存储结构概念图)

上图大概可以这样理解,索引内部由多个段组成,当新文档添加进来时候会生成新的段,不同的段之间可以合并(Segment-0、Segment-1、Segment-2合并成Segment-4),段内含有文档号与文档的索引信息。而每个文档内有多个域可以进行索引,每个域可以指定不同类型(StringField,TextField)。

所以,从图中可以看出,lucene的层次结构依次如下:***索引(Index) –> 段(segment) –> 文档(Document) –> 域(Field) –> 词(Term)***。

在上面我们了解了lucene的一些基本概念,接下来我们进入原理分析的环节。

(为什么lucene搜索引擎查询这么快?)

倒排索引

我们都知道要想提高检索速度要建立索引,重点就在这里,lucene使用了倒排索引(也叫反向索引)的结构。

倒排索引(反向索引)自然就有正排索引(正向索引)。

  • 正排索引是指从文档检索出单词,正常查询的话我们都是从文档里面去检索有没这个关键字单词。

  • 倒排索引是指从单词检索出文档,与从正排索引是倒过来的概念,需要预先为文档准备关键字,然后查询时候直接匹配关键字得到对应的文档。

有一句这样的总结:由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。

image

(具体怎么实现的呀?)

咱们来举个例子来研究一下(例子来源于网络):

假如现在有两个文档,内容分别是:

  • 文档1:home sales rise in July.

  • 文档2:increase in home sales in July.     

image

分析上图可知,首先文档经过分词器(Analyzer)分词之后,我们可以得到词(term),词和文档ID是对应起来的,接下来这些词集进行一次排序,然后合并相同的词并统计出现频率,以及记录出现的文档ID。

所以:

实现时,lucene将上面三列分别作为词典文件(Term Dictionary)、*频率文件(frequencies)、位置文件 (positions)*保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。 

索引时,假设要查询单词 “sales”,lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。  

(原来如此!)

lucne可视化工具Luke

  • https://github.com/DmitryKey/luke/releases

image

构建索引与查询索引过程

以上我们知道了lucene构建索引的原理,接下来我们在代码层面去使用lucene。

我们先来看一张图:

image

检索文件之前先要建立索引,所以上图得从“待检索文件”节点开始看。

构建索引过程:

1、为每一个待检索的文件构建Document类对象,将文件中各部分内容作为Field类对象。

2、使用Analyzer类实现对文档中的自然语言文本进行分词处理,并使用IndexWriter类构建索引。

3、使用FSDirectory类设定索引存储的方式和位置,实现索引的存储。

检索索引过程:

4、使用IndexReader类读取索引。

5、使用Term类表示用户所查找的关键字以及关键字所在的字段,使用QueryParser类表示用户的查询条件。

6、使用IndexSearcher类检索索引,返回符合查询条件的Document类对象。

其中虚线指向的是这个类所在的包名(packege)。如Analyzer在org.apache.lucene.analysis包下。

image

构建索引代码:

//创建索引
public class CreateTest {

    public static void main(String[] args) throws Exception {
        Path indexPath = FileSystems.getDefault().getPath("d:\\index\\");

//        FSDirectory有三个主要的子类,open方法会根据系统环境自动挑选最合适的子类创建
//        MMapDirectory:Linux, MacOSX, Solaris
//        NIOFSDirectory:other non-Windows JREs
//        SimpleFSDirectory:other JREs on Windows
        Directory dir = FSDirectory.open(indexPath);

        // 分词器
        Analyzer analyzer = new StandardAnalyzer();
        boolean create = true;
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
        if (create) {
            indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
        } else {
            // lucene是不支持更新的,这里仅仅是删除旧索引,然后创建新索引
            indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
        }
        IndexWriter indexWriter = new IndexWriter(dir, indexWriterConfig);

        Document doc = new Document();
        // 域值会被索引,但是不会被分词,即被当作一个完整的token处理,一般用在“国家”或者“ID
        // Field.Store表示是否在索引中存储原始的域值
        // 如果想在查询结果里显示域值,则需要对其进行存储
        // 如果内容太大并且不需要显示域值(整篇文章内容),则不适合存储到索引中
        doc.add(new StringField("Title", "sean", Field.Store.YES));
        long time = new Date().getTime();
        // LongPoint并不存储域值
        doc.add(new LongPoint("LastModified", time));
//        doc.add(new NumericDocValuesField("LastModified", time));
        // 会自动被索引和分词的字段,一般被用在文章的正文部分
        doc.add(new TextField("Content", "this is a test of sean", Field.Store.NO));

        List<Document> docs = new LinkedList<>();
        docs.add(doc);

        indexWriter.addDocuments(docs);
        // 默认会在关闭前提交
        indexWriter.close();
    }
}

对应时序图:

image

查询索引代码:

//查询索引
public class QueryTest {

    public static void main(String[] args) throws Exception {
        Path indexPath = FileSystems.getDefault().getPath("d:\\index\\");
        Directory dir = FSDirectory.open(indexPath);
        // 分词器
        Analyzer analyzer = new StandardAnalyzer();

        IndexReader reader = DirectoryReader.open(dir);
        IndexSearcher searcher = new IndexSearcher(reader);

        // 同时查询多个域
//        String[] queryFields = {"Title", "Content", "LastModified"};
//        QueryParser parser = new MultiFieldQueryParser(queryFields, analyzer);
//        Query query = parser.parse("sean");

        // 一个域按词查doc
//        Term term = new Term("Title", "test");
//        Query query = new TermQuery(term);

        // 模糊查询
//        Term term = new Term("Title", "se*");
//        WildcardQuery query = new WildcardQuery(term);

        // 范围查询
        Query query1 = LongPoint.newRangeQuery("LastModified", 1L, 1637069693000L);

        // 多关键字查询,必须指定slop(key的存储方式)
        PhraseQuery.Builder phraseQueryBuilder = new PhraseQuery.Builder();
        phraseQueryBuilder.add(new Term("Content", "test"));
        phraseQueryBuilder.add(new Term("Content", "sean"));
        phraseQueryBuilder.setSlop(10);
        PhraseQuery query2 = phraseQueryBuilder.build();

        // 复合查询
        BooleanQuery.Builder booleanQueryBuildr = new BooleanQuery.Builder();
        booleanQueryBuildr.add(query1, BooleanClause.Occur.MUST);
        booleanQueryBuildr.add(query2, BooleanClause.Occur.MUST);
        BooleanQuery query = booleanQueryBuildr.build();

        // 返回doc排序
        // 排序域必须存在,否则会报错
        Sort sort = new Sort();
        SortField sortField = new SortField("Title", SortField.Type.SCORE);
        sort.setSort(sortField);

        TopDocs topDocs = searcher.search(query, 10, sort);
        if(topDocs.totalHits > 0)
            for(ScoreDoc scoreDoc : topDocs.scoreDocs){
                int docNum = scoreDoc.doc;
                Document doc = searcher.doc(docNum);
                System.out.println(doc.toString());
            }
    }
}

对应时序图:

image

lucene版本信息:

<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>7.4.0</version>
</dependency>

<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-queryparser</artifactId>
    <version>7.4.0</version>
</dependency>

项目应用指南

在实际开发,比较少会直接用lucene,现在主流的搜索框架solr、Elasticsearch都是基于lucene,给我们提供了更加简便的API。特别是在分布式环境中,Elasticsearch可以问我们解决单点问题、备份问题、集群分片等问题,更加符合发展趋势。

至此,整篇完~

image

java思维导图

长按关注,每天java一下,成就架构师

转载于:https://my.oschina.net/u/3080373/blog/2963570

相关文章:

  • “如何让团队成员获得成长?”四名高段位 CTO 为你解惑
  • 二叉树应用
  • Yii2 RULE 校验器
  • 使用xorm工具,根据数据库自动生成 go 代码
  • 服务端渲染(SSR)
  • 2019互联网校招薪资表: BAT、华为还没有TMD高
  • 使用java执行ffmpeg命令进行推流操作
  • vim利用vundle安装YouCompleteMe
  • 高性能负载均衡之分类架构
  • 华为关于多层网络可视化的研究
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • docker-consul
  • 天猫amp;PEPCO 超智能新零售智慧门店横空出世
  • 思否第一天
  • git学习(一)
  • 【Amaple教程】5. 插件
  • 10个确保微服务与容器安全的最佳实践
  • 30秒的PHP代码片段(1)数组 - Array
  • CSS中外联样式表代表的含义
  • exports和module.exports
  • JDK 6和JDK 7中的substring()方法
  • Python学习笔记 字符串拼接
  • Vim 折腾记
  • 分享几个不错的工具
  • 高程读书笔记 第六章 面向对象程序设计
  • 关于for循环的简单归纳
  • 回顾 Swift 多平台移植进度 #2
  • 将 Measurements 和 Units 应用到物理学
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 前端自动化解决方案
  • 收藏好这篇,别再只说“数据劫持”了
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 微信小程序--------语音识别(前端自己也能玩)
  • 详解NodeJs流之一
  • 湖北分布式智能数据采集方法有哪些?
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • (pt可视化)利用torch的make_grid进行张量可视化
  • (ZT)出版业改革:该死的死,该生的生
  • (二)fiber的基本认识
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (七)理解angular中的module和injector,即依赖注入
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)IOS中获取各种文件的目录路径的方法
  • (转)Linq学习笔记
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)
  • .net和php怎么连接,php和apache之间如何连接
  • .net专家(高海东的专栏)
  • [2018/11/18] Java数据结构(2) 简单排序 冒泡排序 选择排序 插入排序
  • [ABC294Ex] K-Coloring
  • [Android] Upload package to device fails #2720
  • [C#]winform制作仪表盘好用的表盘控件和使用方法