Lucene:全文检索技术

Lucene:全文检索技术

  1. 什么是全文检索
  1. 数据的分类
    1. 结构化数据:格式固定、长度固定、数据类型固定,例如数据库中的数据
    2. 非结构化数据:如word、pdf、邮件、html,text格式不固定,长度不固定,数据类型不固定
  2. 数据的查询
    1. 结构化数据的查询:sql语句,查询结构化数据的方法,简单,速度快
    2. 非结构化数据的查询:
      1. 使用程序把文档读入内存,然后匹配字符串,顺序扫描。
      2. 把非结构化数据变成结构化数据。进行结构化数据查询。
        1. 如多个全英文文档,要得到文档中含有指定单词的文档。
        2. 方法:先根据空格进行字符串拆分,得到一个单词列表,基于单词列表创建一个索引。然后根据索引查询,根据单词和文档对应关系找到文档列表,这个过程叫做全文检索。
  3. 全文检索:先创建索引然后查询索引的过程叫做全文检索。索引一次创建可以多次使用,表现为每次查询速度很快。
  1. 全文检索的应用场景
  1. 搜索引擎:百度、谷歌、搜狗等
  2. 站内搜索:论坛搜索、微博、文章搜索
  3. 电商搜索:淘宝搜索、京东搜索
  4. 只要是有搜索的地方,就可以使用全文检索技术。
  1. 什么是Lucene

Lucene是一个基于Java开发全文检索开发工具包。

  1. Lucene实现全文检索的流程
  1. 创建索引

    1. 获得文档

      1. 原始文档:要基于哪些数据来进行搜索,那么这些数据就是原始文档
      2. 获得原始文档技术:
        1. 搜索引擎:使用爬虫获得原始文档
        2. 站内搜索:数据库中的数据
    2. 构建文档对象:

      1. 对应每个原始文档创建一个Document对象,每个document对象中包含多个域(field),域中保存的就是原始文档数据。域的名称:域的值。即key:value形式。在这里插入图片描述
      2. 每个文档都有一个唯一的编号,就是文档id。
    3. 分析文档:就是分词的过程

      1. 根据空格进行字符串拆分,得到一个单词列表

      2. 把单词统一转换成小写

      3. 去除标点符号

      4. 去除停用词(停用词:无意义的词)

        注意:每一个关键词都会封装成一个Term对象中。关键词即分析文档分词后的每个单词

        ​ Term中包含两部分内容:关键词所在的域,关键词本身

        不同的域中拆分出来的相同的关键词是不同的Term。如在文件名称中拆分出的spring和在文件内容中拆分出的spring是不同的Term。

    4. 创建索引:基于关键词列表创建一个索引,保存到索引库中。

      1. 索引库中包含:

        1. 索引
        2. document对象
        3. 关键词和文档的对应关系在这里插入图片描述

        注意:创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫***倒排索引结构***。

        传统方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大、搜索慢。

        倒排索引结构是根据内容(词语)找文档,如下图:

        在这里插入图片描述

        倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。

  2. 查询索引

    1. 用户查询接口:用户输入查询条件的地方,如百度搜索输入框。
    2. 把关键词(查询内容)封装成一个查询对象。
      1. 查询对象包含:
        1. 要查询的域
        2. 要查询的关键词
    3. 执行查询:
      1. 根据要查询的关键词到对应的域上进行搜索。
      2. 找到关键词,根据关键词与文档对应关系找到对应文档。
    4. 渲染结果:根据关键字查询到的文档是文档的id,我们需要根据id找到文档对象,对关键词进行高亮显示,分页处理,最终展示给用户看。
  1. 入门程序
  1. 创建索引

    1. 搭建环境:下载Lucene:https://lucene.apache.org/ 【Jdk要求:1.8以上】
    2. 工程搭建:创建一个Java工程
    <dependencies><dependency><groupId>org.apache.lucenegroupId><artifactId>lucene-coreartifactId><version>7.4.0version>dependency><dependency><groupId>org.apache.lucenegroupId><artifactId>lucene-analyzers-commonartifactId><version>7.4.0version>dependency><dependency><groupId>commons-iogroupId><artifactId>commons-ioartifactId><version>2.6version>dependency><dependency><groupId>junitgroupId><artifactId>junitartifactId><version>4.12version>dependency><dependency><groupId>org.apache.lucenegroupId><artifactId>lucene-queriesartifactId><version>7.4.0version>dependency>dependencies>
    
    1. 创建索引步骤

      1. 创建一个Director对象,指定索引库保存的位置
      2. 基于Directory对象创建一个IndexWriter对象
      3. 读取磁盘上的文件,对应每个文件创建一个文档对象
      4. 创建域Filed,存放文件的相关信息
      5. 创建文档对象,并把域存入文档对象中
      6. 把文档对象写入索引库
      7. 关闭indexWriter对象

      代码如下:

      public void createIndex() throws Exception{//1. 创建一个Director对象,指定索引库保存的位置//把索引库保存在内存:Directory directory = new RAMDirectory();//把索引库保存在磁盘中Directory directory = FSDirectory.open(new File("E:\\Java\\Lucene\\Demo").toPath());//2. 基于Directory对象创建一个IndexWriter对象IndexWriter indexWriter = new IndexWriter(directory,new IndexWriterConfig());//3. 读取磁盘上的文件,对应每个文件创建一个文档对象File dir = new File("D:\\Learn Course\\61.会员版(2.0)-就业课(2.0)-Lucene\\lucene\\02.参考资料\\searchsource");File[] files = dir.listFiles();for (File file: files) {//读取文件名String fileName = file.getName();System.out.println(fileName);//读取文件路径String filePath = file.getPath();//读取文件内容String fileContent = FileUtils.readFileToString(file, "utf-8");//读取文件大小long fileSize = FileUtils.sizeOf(file);//4.创建域Filed,存放文件的相关信息//参数1:域的名称(自定)参数2:域的内容(字符串格式) 参数3:是否存储Field fieldName = new TextField("name",fileName,Field.Store.YES);Field fieldPath = new TextField("path",filePath,Field.Store.YES);Field fieldContent = new TextField("content",fileContent,Field.Store.YES);Field fieldSize = new TextField("size",fileSize+"",Field.Store.YES);//5. 创建文档对象,并把域存入文档对象中Document document = new Document();document.add(fieldName);document.add(fieldPath);document.add(fieldContent);document.add(fieldSize);//5. 把文档对象写入索引库indexWriter.addDocument(document);}//6. 关闭indexWriter对象indexWriter.close();}
      
    2. 可使用luke查看索引库 要求:必须Java9

  2. 查询索引

    1. 创建一个Director对象,指定索引库的位置
    2. 创建一个IndexReader对象
    3. 创建一个IndexSeacher对象,构造方法中的参数indexReader对象
    4. 创建一个Query对象,TermQuery
    5. 执行查询,得到一个TopDocs对象
    6. 取查询结果的总记录数
    7. 取文档列表
    8. 打印文档内容
    9. 关闭indexReader对象

    代码实现:

    public void indexRead() throws Exception{//1. 创建一个Director对象,指定索引库的位置Directory directory = FSDirectory.open(new File("E:\\Java\\Lucene\\Demo").toPath());//2. 创建一个IndexReader对象IndexReader indexReader = DirectoryReader.open(directory);//3. 创建一个IndexSeacher对象,构造方法中的参数indexReader对象IndexSearcher indexSearcher = new IndexSearcher(indexReader);//4. 创建一个Query对象,TermQuery(用来指定查询的内容)//Term对象有两部分:所在的域名称:term本身(即要查询的关键字)在这里即要查询关键字为:springQuery query = new TermQuery(new Term("content","spring"));//5. 执行查询,得到一个TopDocs对象//参数1:查询对象,参数2:查询结果返回的最大记录数(自定义)TopDocs topDocs = indexSearcher.search(query, 6);//6. 取查询结果的总记录数//topDocs.totalHits:总命中条数System.out.println("查询的总记录数:"+topDocs.totalHits);//7. 取文档列表ScoreDoc[] scoreDocs = topDocs.scoreDocs;//8. 打印文档内容for (ScoreDoc scoreDoc : scoreDocs) {int doc = scoreDoc.doc;//文档idDocument document =  indexSearcher.doc(doc);System.out.println(document.get("name"));System.out.println(document.get("path"));//System.out.println(document.get("content"));System.out.println(document.get("size"));System.out.println("======================================");}//9. 关闭indexReader对象indexReader.close();}
    
  1. 分析器:用来分析文档,即分词
  1. 默认使用的是标准分析器:StandardAnalyzer .标准分析器对英文分析没用问题,但是不能对中文进行有效分析

  2. 查看标准分析器的分析结果

    1. 使用Analyzer对象的tokenStream方法返回一个TokenStream对象。此对象中 包含了最终分词的结果。
    2. 实现步骤:
      1. 创建一个Analyzer对象,StandardAnalyzer对象。
      2. 使用分析器对象的tokenStream方法获得一个TokenStream对象
      3. 向TokenStream对象中设置一个引用CharTermAttribute,相当于一个指针。返回的值CharTermAttribute内包含的就是分析结果
      4. 调用TokenStream对象的reset方法。不调用会报错,用来把指针归于开始位置
      5. 使用while循环遍历TokenStream对象
      6. 关闭TokenStream对象。
    3. 代码实现:
    public void testStandardAnalyzer()throws Exception{//        1. 创建一个Analyzer对象,StandardAnalyzer对象。Analyzer analyzer = new StandardAnalyzer();//        2. 使用分析器对象的tokenStream方法获得一个TokenStream对象//参数1:域的名称 参数2:要分析的内容TokenStream tokenStream = analyzer.tokenStream("", "The Spring Framework provides a comprehensive programming and configuration model.");//3. 向TokenStream对象中设置一个引用CharTermAttribute,相当于一个指针。返回的值CharTermAttribute内包含的就是分析结果CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);//        4. 调用TokenStream对象的reset方法。不调用会报错,用来把指针归于开始位置tokenStream.reset();//        5. 使用while循环遍历TokenStream对象while (tokenStream.incrementToken()){System.out.println(charTermAttribute.toString());}//        6. 关闭TokenStream对象。tokenStream.close();}
    
  3. 中文分析器:IKAnalyzer的使用方法

    1. 把IKAnlyzer的jar包添加到工程中
    2. 把配置文件和扩展词典,以及配置文件添加到工程的classpath下(maven工程则直接加到resouces下)

    注意:扩展词典严禁使用windows记事本编辑以保证扩展词典的编码格式是utf-8。而记事本默认保存格式为utf-8BOM。

    扩展词典(hotwords.dic):用于添加一些新词,以在分析器分析该词时候,将其作为一个词语看待。

    停用词词典(stopwords.dic):无意义的词或者是敏感词汇添加进去,在分析器分析时,会过滤掉这些词。

    1. 代码展示

      1. 扩展词典以及停用词词典的配置文件
      
        
      <properties>  <comment>IK Analyzer 扩展配置comment><entry key="ext_dict">hotword.dic;entry><entry key="ext_stopwords">stopword.dic;entry> properties>	  
      
      1. IKAnlyzer分词测试代码
      public void testIKAnlyzer() throws Exception{//        1. 创建一个Analyzer对象,IKAnalyzer对象。Analyzer analyzer = new IKAnalyzer();
      //        2. 使用分析器对象的tokenStream方法获得一个TokenStream对象//参数1:域的名称 参数2:要分析的内容TokenStream tokenStream = analyzer.tokenStream("", "传智播客黑马程序员测试非常的好用, 正文 在Reimpot All Maven Porjects时, 如果项目过大, maven依赖过多, 会直接卡在");
      //        3. 向TokenStream对象中设置一个引用CharTermAttribute,相当于一个指针。返回的值CharTermAttribute内包含的就是分析结果CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
      //        4. 调用TokenStream对象的reset方法。不调用会报错,用来把指针归于开始位置tokenStream.reset();
      //        5. 使用while循环遍历TokenStream对象while (tokenStream.incrementToken()){System.out.println(charTermAttribute.toString());}
      //        6. 关闭TokenStream对象。tokenStream.close();}
      

      注意:原本传智播客不是一个词语,因将其手动添加到扩展词典后,在分析时,会将其当作一个词语分析出来。

      运行结果:

      加载扩展词典:hotword.dic
      加载扩展停止词典:stopword.dic
      传智播客
      黑马
      程序员
      程序
      测试
      非常
      ...
      
      • 可见会先加载扩展词典以及停用词典。

      • IKAnlyzer分析器的使用和StandardAnlyzer分析器的使用方式相同。

    2. IKAnalyzer的使用方式

    public void createIndex() throws Exception{//1. 创建一个Director对象,指定索引库保存的位置//把索引库保存在内存:Directory directory = new RAMDirectory();//把索引库保存在磁盘中Directory directory = FSDirectory.open(new File("E:\\Java\\Lucene\\Demo").toPath());//2. 基于Directory对象创建一个IndexWriter对象IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());IndexWriter indexWriter = new IndexWriter(directory,config);//3. 读取磁盘上的文件,对应每个文件创建一个文档对象File dir = new File("D:\\Learn Course\\61.会员版(2.0)-就业课(2.0)-Lucene\\lucene\\02.参考资料\\searchsource");File[] files = dir.listFiles();for (File file: files) {//读取文件名String fileName = file.getName();//读取文件路径String filePath = file.getPath();//读取文件内容String fileContent = FileUtils.readFileToString(file, "utf-8");//读取文件大小long fileSize = FileUtils.sizeOf(file);//4.创建域Filed,存放文件的相关信息//参数1:域的名称(自定)参数2:域的内容(字符串格式) 参数3:是否存储Field fieldName = new TextField("name",fileName,Field.Store.YES);Field fieldPath = new TextField("path",filePath,Field.Store.YES);Field fieldContent = new TextField("content",fileContent,Field.Store.YES);Field fieldSize = new TextField("size",fileSize+"",Field.Store.YES);//5. 创建文档对象,并把域存入文档对象中Document document = new Document();document.add(fieldName);document.add(fieldPath);document.add(fieldContent);document.add(fieldSize);//5. 把文档对象写入索引库indexWriter.addDocument(document);}//6. 关闭indexWriter对象indexWriter.close();}
    

    注意:

    • IKAnlyzer分析器的使用和StandardAnlyzer分析器的使用方式相同。仅仅是在IndexWriterConfig中指定了IK分析器。原来没用指定,则默认使用的是StandardAnlyzer分析器。
    • 查询索引方式完全相同。
  1. 索引库的维护(对索引库中文档的增删改操作)
  1. Filed域的属性

    1. 是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。

    2. 是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。

      比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。

    3. 是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取

      比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

    4. 是否存储的标准:是否要将内容展示给用户

    Field类数据类型Analyzed是否分析Indexed是否索引Stored是否存储说明
    StringField(FieldName, FieldValue,Store.YES))字符串NYN/Y这个Field用来构建一个字符串Field,但是不会进行分析,会将整个串存储在索引中,比如(订单号,姓名等) 是否存储在文档中用Store.YES或Store.NO决定
    LongPoint(String name, long… point)Long型YYN可以使用LongPoint、IntPoint等类型存储数值类型的数据。让数值类型可以进行索引。但是不能存储数据,如果想存储数据还需要使用StoredField。
    StoredField(FieldName, FieldValue)重载方法,支持多种类型NNY这个Field用来构建不同类型Field 不分析,不索引,但要Field存储在文档中
    TextField(FieldName, FieldValue, Store.NO) 或 TextField(FieldName, reader)字符串或流YYN/Y如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略

    修改域后代码

        //4.创建域Filed,存放文件的相关信息//参数1:域的名称(自定)参数2:域的内容(字符串格式) 参数3:是否存储Field fieldName = new TextField("name",fileName,Field.Store.YES);Field fieldPath = new StringField("path",filePath,Field.Store.YES);Field fieldContent = new TextField("content",fileContent,Field.Store.YES);//将Long型数据使用LongPoint域,但是不会存储,无法取出。只做运算使用Field fieldSizeValue = new LongPoint("size",fileSize);//使用StoreField域对Long型数据进行存储。取得时候即可取出Field fieldSizeStore = new StoredField("size",fileSize);//5. 创建文档对象,并把域存入文档对象中Document document = new Document();document.add(fieldName);document.add(fieldPath);document.add(fieldContent);document.add(fieldSizeValue);document.add(fieldSizeStore);...
    
  2. 索引库中添加文档

public void addDocument() throws Exception {//索引库存放路径Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());//创建一个indexwriter对象IndexWriter indexWriter = new IndexWriter(directory, config);//创建一个Document对象Document document = new Document();//向document对象中添加域。//不同的document可以有不同的域,同一个document可以有相同的域。document.add(new TextField("filename", "新添加的文档", Field.Store.YES));document.add(new TextField("content", "新添加的文档的内容", Field.Store.NO));//LongPoint创建索引document.add(new LongPoint("size", 1000l));//StoreField存储数据document.add(new StoredField("size", 1000l));//不需要创建索引的就使用StoreField存储document.add(new StoredField("path", "d:/temp/1.txt"));//添加文档到索引库indexWriter.addDocument(document);//关闭indexwriterindexWriter.close();
}

  1. 索引库的删除

    1. 删除全部索引:删除所有的文档
    public void deleteAllIndex() throws Exception {IndexWriter indexWriter = getIndexWriter();//删除全部索引indexWriter.deleteAll();//关闭indexwriterindexWriter.close();}
    

    注意:将索引目录的索引信息全部删除,直接彻底删除,无法恢复。慎用!

    1. 指定查询条件删除:删除包含指定 关键字的所有文档
    public void deleteIndexByQuery() throws Exception {IndexWriter indexWriter = getIndexWriter();//创建一个查询条件Query query = new TermQuery(new Term("filename", "apache"));//根据查询条件删除indexWriter.deleteDocuments(query);//关闭indexwriterindexWriter.close();}
    
    1. 修改索引库:原理:先删除包含指定查询关键字的所有文档后添加新创建的文档。
    public void updateIndex() throws Exception {IndexWriter indexWriter = getIndexWriter();//创建一个新的Document对象Document document = new Document();//向document对象中添加域。//不同的document可以有不同的域,同一个document可以有相同的域。document.add(new TextField("filename", "要更新的文档", Field.Store.YES));document.add(new TextField("content", " Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包," +"它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。",Field.Store.YES));indexWriter.updateDocument(new Term("content", "java"), document);//关闭indexWriterindexWriter.close();
    }
    
  1. 索引库的查询
  1. 使用Query的子类

    1. TermQuery:根据关键词进行查询,需要指定要查询的域及要查询的关键词
    2. LongPoint.newRangeQuery()方法执行范围查询
      1. 注意:如果是Long类型的,就使用LongPoint,如果是Int,则使用IntPoint。
    package com.itlearn.lucene;import org.apache.lucene.document.LongPoint;
    import org.apache.lucene.index.DirectoryReader;
    import org.apache.lucene.index.IndexReader;
    import org.apache.lucene.search.*;
    import org.apache.lucene.store.FSDirectory;
    import org.apache.lucene.document.Document;
    import org.junit.Before;
    import org.junit.Test;import java.io.File;public class LuceneQuery {private IndexReader indexReader;private IndexSearcher indexSearcher;@Beforepublic void init() throws Exception{indexReader = DirectoryReader.open(FSDirectory.open(new File("E:\\Java\\Lucene\\Demo").toPath()));indexSearcher = new IndexSearcher(indexReader);}public void printResult(Query query) throws Exception{//执行查询,得到总记录条数TopDocs topDocs = indexSearcher.search(query, 10);//输出总记录条数System.out.println("总记录条数"+topDocs.totalHits);//取文档列表ScoreDoc[] scoreDocs = topDocs.scoreDocs;//遍历文档列表,得到文档信息for (ScoreDoc scoreDoc : scoreDocs) {//取出文档idint docId = scoreDoc.doc;//根据文档id取出文档Document document = indexSearcher.doc(docId);//获取文档相关信息System.out.println(document.get("name"));System.out.println(document.get("path"));System.out.println(document.get("size"));//System.out.println(document.get("content"));System.out.println("================");}indexReader.close();}@Testpublic void testRangeQuery() throws Exception{//创建一个query对象,查询size大小在0-100之间的所有文档Query query = LongPoint.newRangeQuery("size",0l,100l);printResult(query);}
    }
    
    1. QueryParser进行查询:原理:先对查询条件进行分词,再根据分词之后的结果进行查询

      注意:使用QueryParser需要导入jar包:lucene-queryparser-7.4.0.jar

      public void testQueryParser() throws Exception {IndexSearcher indexSearcher = getIndexSearcher();//创建queryparser对象//第一个参数默认搜索的域//第二个参数就是分析器对象QueryParser queryParser = new QueryParser("content", new IKAnalyzer());Query query = queryParser.parse("Lucene是java开发的");//执行查询printResult(query, indexSearcher);
      }private void printResult(Query query, IndexSearcher indexSearcher) throws Exception {//执行查询TopDocs topDocs = indexSearcher.search(query, 10);//共查询到的document个数System.out.println("查询结果总数量:" + topDocs.totalHits);//遍历查询结果for (ScoreDoc scoreDoc : topDocs.scoreDocs) {Document document = indexSearcher.doc(scoreDoc.doc);System.out.println(document.get("filename"));//System.out.println(document.get("content"));System.out.println(document.get("path"));System.out.println(document.get("size"));}//关闭indexreaderindexSearcher.getIndexReader().close();
      }
      

到的document个数

      System.out.println("查询结果总数量:" + topDocs.totalHits);//遍历查询结果for (ScoreDoc scoreDoc : topDocs.scoreDocs) {Document document = indexSearcher.doc(scoreDoc.doc);System.out.println(document.get("filename"));//System.out.println(document.get("content"));System.out.println(document.get("path"));System.out.println(document.get("size"));}//关闭indexreaderindexSearcher.getIndexReader().close();}```


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部