zoukankan      html  css  js  c++  java
  • 搜索引擎--Lucene

    前言:
    1.搜索技术:
    1.1 搜索引擎的种类
    搜索引擎按照功能通常分为垂直搜索和综合搜索
    ①垂直搜索是指专门针对某一类信息进行搜索
    ②综合搜索是指对众多信息进行综合性的搜索
     
    1.2 倒排索引
    倒排索引又称为反向索引,有id到关键词的映射变为有关键词到ID的映射,加快了查找的效率
     
    Lucene:
        Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供
        Lucene提供了一个简单而强大的应用程序API,能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码的工具
        Lucene不是现成的搜索引擎产品,但是可以用来制作搜索引擎产品
     
    全文检索:
        计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
        Lucene全文检索就是对文档中全部内容进行分词,然后对所有单词建立倒排索引的过程。
     
    Lucene下载的网址:https://www.apache.org/
     
    Lucene的基本使用
        使用lucene的api来实现对索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)
    ①创建索引
    创建索引过程中涉及的一些名词:
    文档Document:数据库中一条具体的记录
    字段Field:数据库中的每个字段
    目录对象Directory:物理存储位置
    写出器的配置对象:需要分词器和Lucene的版本
     
    对应的jar包:
    <!-- 添加Lucene全文检索 -->
    <!-- lucene核心库 -->
    <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-core</artifactId>
       <version>8.6.2</version>
    </dependency>
    <!-- lucene的查询解析器 -->
    <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-queryparser</artifactId>
       <version>8.6.2</version>
    </dependency>
    <!-- lucene的默认的分词器库 -->
    <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-analyzers-common</artifactId>
       <version>8.6.2</version>
    </dependency>
    <!-- lucene的高亮显示 -->
    <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-highlighter</artifactId>
       <version>8.6.2</version>
    </dependency>
    Lucene-core中自带的分词器有下面几种:(没有IKAnalyzer),对中文的支持都不怎么好,所以我们需要进入新的分词器:IK分词器
    <!-- ik分词器 -->
    <dependency>
        <groupId>com.github.magese</groupId>
        <artifactId>ik-analyzer</artifactId>
        <version>8.3.0</version>
    </dependency>
    注意:IK分词器jar包和Lucene的jar包的版本匹配,以及Lucene和jdk版本之前匹配的问题,自己尝试的时候,因为jar包问题就浪费好多时间
             Lucene6+版本就需要使用JDK1.8了
     
    1.创建索引
    代码实现来创建索引:
    /**
    * 使用Lucene创建索引
    * @author xiaoxu
    *
    * 步骤:
    * 1.创建文档对象
    * 2.创建存储目录
    * 3.创建分词器
    * 4.创建索引写入器的配置对象
    * 5.创建索引写入器对象
    * 6.将文档交给索引写入器
    * 7.提交
    * 8.关闭
    *
    */
    public class CreateLuceneIndex {
         
          private static final Logger LOG =  LoggerFactory.getLogger(CreateLuceneIndex.class);
         
          private static final int SIZE = 1000;
         
          private static final String INDEX_PATH = "G:\Lucene\luceneDir";
         
          //创建索引
          public static void createIndex() throws Exception{
                //获取数据的总条数
                int count =  KqMbSessionUtil.selectOne("luceneMapper.getRegnZbDataCount");
                LOG.info("需要创建索引的数据条数为{}条",count);
                while(count>0) {
                       //从数据库中获取需要创建索引的数据集合
                       List<Map<String,Object>> dataList =  KqMbSessionUtil.selectList("luceneMapper.getNeedCreateIndexData",SIZE);
                       
                       //创建文档的集合
                       List<Document> docs = new ArrayList<>();
                       for(Map<String,Object> djbDataMap:dataList) {
                              //创建文档对象
                              Document document = new Document();
                              //创建并添加字段信息。参数:字段的名称、字段值、是否存储,yes代表存储
                              document.add(new StringField("gid",  Convert.toStr(djbDataMap.get("gid")), Field.Store.YES));
                              //zl使用TextField,即创建索引又会被分词。StringField会创建索引,但不会分词
                              document.add(new TextField("zl",  Convert.toStr(djbDataMap.get("zl")), Field.Store.YES));
                              docs.add(document);
                       }
                       
                       //索引目录类,指定索引在硬盘中的位置
                       Directory directory =  FSDirectory.open(Paths.get(INDEX_PATH));
                       //引入IK分词器
                       Analyzer analyzer = new IKAnalyzer();
                       //analyzer.setVersion(Version.LATEST);
                       //索引写出工具的配置对象
                       IndexWriterConfig config = new IndexWriterConfig(analyzer);
                       //设置打开方式:OpenMpde.APPEND 会在索引库的基础上追加新的索引;OpenMode.CREATE会先清空原来数据,再提交新的索引
                       //第一次执行时因为没有索引库和索引文件,所以append会报错,可以先使用create commit之后在执行,或者使用CREATE_OR_APPEND
                       config.setOpenMode(IndexWriterConfig.OpenMode.APPEND);
                       
                       //创建索引的写出工具类。参数:索引目录和配置信息
                       IndexWriter indexWriter = new IndexWriter(directory,  config);
                       
                       //把文档集合交给IndexWriter(索引写入器)
                       indexWriter.addDocuments(docs);
                       //提交
                       indexWriter.commit();
                       //关闭
                       indexWriter.close();
                       
                       KqMbSessionUtil.update("luceneMapper.updateLuceneIndex",  dataList);
                }
               
          }
           
    }
    结合实际情况,我需要给我这边坐落的字段添加全文索引,但是数据库中存在的数据已经是亿级了,这里就批量的创建索引了。
     
    创建索引的API讲解
    Document:文档对象
       相当于数据库中的一条记录,有id和content字段等。
     
    Field:字段类
       一个Document中可以有很多不同的字段,每个字段都是一个Field类的对象。
       一个Document中对应的字段其类型是不确定的,因此Field类提供了各种不同的子类,来对应这些字段,如DoubleField、FloatField、StoredField、                TextField、StringField等等。
       字段类型的区别如下:
       TextField:一定会被索引,一定会分词;
       StoredField:一定会存储,一定不会被索引;
       DoubleField、FloatField、IntField、StringField等:一定会索引,但是一定不会分词;
     
    Directory:目录类
        指定索引要存放的位置
        有2中子类来确定索引存在的方式:
        ①FSDirectory:文件系统目录,会把索引指向本地磁盘;特点:速度略慢,但是比较安全
        ②RAMDirectory:内存目录,会把索引库保存在内存中;特点:速度块,但是不安全
    Analyzer:分词器
        因为Lucene自带的分词器对中文的支持不是太好,所以引入了IK分词器,即IKAnalyzer
    IndexWriterConfig:索引写出器配置类
    ①加载引入的分词器
    //索引写出工具的配置对象
    IndexWriterConfig config = new IndexWriterConfig(analyzer)
    ②设置往索引库中添加的方式
    //设置打开方式:OpenMpde.APPEND 会在索引库的基础上追加新的索引;OpenMode.CREATE会先清空原来数据,再提交新的索引
    //第一次执行时因为没有索引库和索引文件,所以append会报错,可以先使用create commit之后在执行,或者使用CREATE_OR_APPEND
    config.setOpenMode(IndexWriterConfig.OpenMode.APPEND);
    IndexWriter:索引写出器类
        加载索引写出器配置类,是索引写出工具,实现对索引的增删改查操作
    //创建索引的写出工具类。参数:索引目录和配置信息
    IndexWriter indexWriter = new IndexWriter(directory,  config);
                       
    //把文档集合交给IndexWriter(索引写入器)
    indexWriter.addDocuments(docs);
    //提交
    indexWriter.commit();
    //关闭
    indexWriter.close();
    2.查询索引数据
    代码实现:
    /**
    * 查询索引数据
    * 1.创建读取目录对象
    * 2.创建索引读取工具
    * 3.创建索引搜索工具
    * 4.创建查询解析器
    * 5.创建查询对象
    * 6.搜索数据
    * 7.对数据的各种操作
    */
    public static void  queryLuceneIndex() throws Exception{
          //索引目录对象
          Directory directory = FSDirectory.open(Paths.get(INDEX_PATH));
          //索引读取工具
          IndexReader indexReader = DirectoryReader.open(directory);
          //索引搜索工具
          IndexSearcher indexSearcher = new IndexSearcher(indexReader);
         
          //创建查询解析器(2个参数:查询字段的名称,分词器)
          QueryParser queryParser = new QueryParser("zl", new IKAnalyzer());
          //创建查询对象
          Query query = queryParser.parse("官渡区");
         
          //搜索数据,2个参数:查询条件对象;查询的最大结果条数
          TopDocs topDocs = indexSearcher.search(query, 10);
         
          //获取总条数
          long total = topDocs.totalHits.value;
          System.out.println("本次搜索共找到" + total + "条数据");
         
          //获取得分文档对象(ScoreDoc)数组,ScoreDoc中包含:文档的编号,文档的得分
          ScoreDoc [] scoreDocs = topDocs.scoreDocs;
          for(ScoreDoc score:scoreDocs) {
                //取出文档编号
                int docId = score.doc;
                //根据编号去找文档
                Document doc = indexReader.document(docId);
               
                System.out.println("id="+doc.get("id")+"*******zl="+doc.get("zl")+"*********得分="+score.score);
          }
    }
    查询索引的核心API
    QueryParser:查询解析器
    //创建查询解析器(2个参数:查询字段的名称,分词器)
    QueryParser queryParser = new QueryParser("zl", new IKAnalyzer());
    //创建查询对象
    Query query = queryParser.parse("官渡区");
    MultiFieldQueryParser:多字段的查询解析器
     
    Query:查询对象,包含要查询的关键词信息
    query中包含很多子类,可以直接创建查询对象,实现高级查询
     
    IndexSearch:索引搜索对象,执行搜索功能
    IndexSearch可以帮助我们实现快速搜索、排序、打分等功能
    IndexSearch需要依赖IndexReader类
     
    TopDocs:查询结果对象
    通过IndexSearch对象搜索获得的结果就是TopDocs对象
    在TopDocs中,包括两部分信息:
        totalHits:查询到的总条数
        ScoreDoc[]:得分文档对象的数组
     
    ScoreDoc:得分文档对象
    ScoreDoc是得分文档对象,包含两部分数据:
        int    doc:文档编号
        float    score:文档的得分信息
    拿到编号后,可以根据编号获取文档的信息
     
    特殊查询
        ①TermQuery:词条查询
            词条:就是数据分词后得到的每一个词,是分词的最小单位,不能继续分。
            场景:如果一个字段不需要分词的,那么我们一般使用词条查询,例如:id
            Lucene中,Term要求字段的类型必须是字符串
    //索引目录对象
    Directory directory = FSDirectory.open(Paths.get(INDEX_PATH));
    //索引读取工具
    IndexReader indexReader = DirectoryReader.open(directory);
    //索引搜索工具
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    //创建查询解析器(2个参数:查询字段的名称,分词器)
    //QueryParser queryParser = new QueryParser("zl", new IKAnalyzer());
    //创建查询对象
    //Query query = queryParser.parse("官渡区");
    Query query = new TermQuery(new Term("zl", "官渡区"));
    //搜索数据,2个参数:查询条件对象;查询的最大结果条数
    TopDocs topDocs = indexSearcher.search(query, 10);
    ②WildcardQuery:通配符查询
            测试通配符查询
            ?    代表任意一个字符
            *      代表任意多个字符
    Query query = new WildcardQuery(new Term("zl", "官*区"));
    //搜索数据,2个参数:查询条件对象;查询的最大结果条数
    TopDocs topDocs = indexSearcher.search(query, 10);
     
    ②FuzzyQuery:模糊查询
    //创建模糊查询对象:允许用户输错,但是要求错误的最大编辑距离不能超过2
    //编辑距离:一个单词到另一个单词最少要修改的次数 facebool  ---> facebook  需要编辑1次
    Query query = new FuzzyQuery(new Term("title", "fscevool"));
    //可以手动指定编辑距离,但是参数必须在0-2之间
    Query query = new FuzzyQuery(new Term("title", "fscevool"),1);
     
    ③NumericRangeQuery:数值范围查询
    /**
    *
    *注意:数值范围查询,可以用来对非String类型的ID进行精确查找
    */
    //数值范围查询对象,参数:字段名称、最小值、最大值、是否包含最小值、是否包含最大值
    Query query = NumericRangeQuery.newLongRange("id",2L,2L,true,true);
     
    另外还有组合查询等等,需要用到的可以自行探索
     
     
    3.修改索引
    对应的代码如下:
    /**
    * 修改索引
    *
    * 1.创建文档存储目录
    * 2.创建索引写入器配置对象
    * 3.创建索引写入器
    * 4.创建文档数据
    * 5.修改
    * 6.提交
    * 7.关闭
    *
    * 注意:
    * A:Lucene修改功能底层会先删除,再把新的文档添加
    * B:修改功能会根据Term进行匹配,所有匹配到的都会被删除,这样不好
    * C:因此,我们修改时,都会根据一个唯一不重复字段进行匹配修改,例如主键字段
    * D:但是词条搜索,要求ID必须是字符串,可以先手动删除deleteDocuments(数值范围查询锁定ID)
    */
    public static void editLuceneIndex() throws Exception{
           //创建目录对象
           Directory directory = FSDirectory.open(Paths.get(INDEX_PATH));
           //创建配置对象
           IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
           //创建索引写入工具
           IndexWriter writer = new IndexWriter(directory, config);
           
           //创建新的文档数据
           Document document = new Document();
           document.add(new StringField("id", "1", Store.YES));
           document.add(new TextField("zl", "官渡区民航路26号香樟俊园三期2幢3层307室",  Store.YES));
           
           //修改索引
           writer.updateDocument(new Term("id","1"), document);
           writer.commit();
           writer.close();
    }
     
    4.索引删除数据
    代码如下:
    /**
    * 删除索引
    *
    * 1.创建文档对象目录
    * 2.创建索引写入器配置对象
    * 3.创建索引写入器
    * 4.删除
    * 5.提交
    * 6.关闭
    *
    * 注意:
    * 一般,为了进行精确删除,我们会根据唯一字段来删除,比如:ID
    * 如果使用Term删除,要求ID也必须是字符串类型
    */
    public static void deleteLuceneIndex() throws Exception{
           //创建文档目录对象
           Directory directory = FSDirectory.open(Paths.get(INDEX_PATH));
           //创建索引写入配置对象
           IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
           //创建索引写入对象
           IndexWriter writer = new IndexWriter(directory, config);
           
           //根据词条进行删除
           writer.deleteDocuments(new Term("id","1"));
           //删除所有
           //writer.deleteAll();
           writer.commit();
           writer.close();
    }
     
    另外关于Lucene中的其他应用,比如高亮显示、排序、分页、得分算法等,这些方法在API中都是存在的,因为个人没有用到这些功能,所以此处就不一一列举了
     
  • 相关阅读:
    JVM实战---类加载的过程
    MobaXterm:远程终端登录软件封神选手
    Linux内核实战(二)- 操作系统概览
    Linux再学习(一)-学习路线规划
    Flink实战(八)
    Docker实战之Redis-Cluster集群
    通过乐观锁解决库存超卖的问题
    Docker实战之MySQL主从复制
    JVM类加载器是否可以加载自定义的String
    设计模式--单例
  • 原文地址:https://www.cnblogs.com/ammyblog/p/13713541.html
Copyright © 2011-2022 走看看