搜索引擎--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中都是存在的,因为个人没有用到这些功能,所以此处就不一一列举了
 
原文地址:https://www.cnblogs.com/ammyblog/p/13713541.html