Lucene入门

Lucene

一、 全文检索

  • 数据的分类和查询方法

    1. 数据的分类

      • 结构化数据

        例如数据库中的数据。

        格式固定、长度固定、数据类型固定。

      • 非结构化数据

        word 文档、pdf 文档、邮件、html、txt

        格式不固定、长度不固定、数据类型不固定。

    2. 数据的查询

      • 结构化数据的查询

        SQL 语句,查询结构化数据的方法。简单、速度快。

      • 非结构化数据的查询

        例:从文本文件中找出包含 Spring 单词的文件:

        1. 目测法。

        2. 使用程序把文档读取到内存中,然后匹配字符串,顺序扫描。

        3. 把非结构化数据变成结构化数据:

          先根据空格进行字符串拆分,得到一个单词列表,基于单词列表创建一个索引,然后查询索引,根据单词和文档的对应关系找到文档列表。这个过程就叫做全文检索。

  • 全文检索定义

    先创建索引然后查询索引的过程就叫做全文检索。索引一次创建可以多次使用,表现为每次查询速度很快。

  • 全文检索应用场景

    1. 搜索引擎

      百度、360搜索、谷歌、搜狗。

    2. 站内搜索

      论坛内搜索、微博搜索、文章搜索、淘宝搜索、京东搜索。

    3. 只要是有搜索的地方就可以使用全文检索技术。

  • Lucene

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

二、 Lucene 实现全文检索的流程

  • 索引和搜索流程图

    在这里插入图片描述

2.1 创建索引
  1. 获得文档

    原始文档:要基于哪些数据来进行搜索,那么这些数据就是原始文档。

    搜索引擎:使用爬虫获取原始文档。

    站内搜索:数据库中的数据。

  2. 构建文档对象

    对应每个原始文档创建一个 Document 对象,每个 Document 对象中包含多个域 (field),域中保存的就是原始文档数据。域的名称和域的值构成一对键值对。

    每个文档都有一个唯一的编号,也就是文档的 id。

    在这里插入图片描述

  3. 分析文档

    就是分词过程

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

    2. 把单词统一转换成小(或大)写。

    3. 去除标点符号。

    4. 去除停用词(也就是无意义的词)。

    每个关键字都封装成一个 Term 对象。Term 对象中包含两部分内容:一是关键字所在的域、二是关键词本身,不同的域中拆分出来的相同的关键词是不同的 Term。

  4. 创建索引

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

    索引库中包含:

    • 索引

    • Document 对象

    • 关键词和文档的对应关系

    通过词语找文档,这种索引的结构叫做倒排索引结构。

2.2 查询索引
  1. 用户查询接口

    用户输入查询条件的地方。例如:百度的搜索框。

  2. 把关键词封装成一个查询对象

    查询对象包含:要查询的域,要搜索的关键字。

  3. 执行查询

    根据要查询的关键词到对应的域上进行搜索。找到关键词,根据关键词找到对应的文档。

  4. 渲染结果

    根据文档的 id 找到文档的对象,对关键词进行高亮显示、分页处理。

三、 入门案例(简单使用 Lucene)

3.1 导入依赖 jar 包

* lucene-analyzer-commom-7.4.0.jar
* lucene-core-7.4.0.jar
* commons-io.jar

3.2 建立索引库

  1. 创建一个 Directory 对象;

  2. 基于 Directory 对象创建一个 IndexWriter 对象;

  3. 读取磁盘上的文件,对应每一个文件创建一个文档对象;

  4. 向文档对象中添加域;

  5. 把文档对象写入索引库;

  6. 关闭 IndexWriter 对象。

    /**
     * 1. 建立索引库,并把文档映射到索引
     * */
    @Test
    public void buildIndexStore() throws IOException {
        // 1. 创建一个 Directory 对象,指定索引库的位置
        Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath());
        // 2. 创建 写索引 对象
        IndexWriterConfig config = new IndexWriterConfig();
        IndexWriter indexWriter = new IndexWriter(directory, config);
    
        // 3. 读取磁盘上的文件,对应每个文件创建一个文档对象。
        File file = new File("E:\\java\\15_Lucene\\01_lucene_demo\\searchsource");
        File[] files = file.listFiles();
    
        for (File f:
             files) {
            // 获取文件名
            String fileName = f.getName();
            System.out.println(fileName);
            // 获取文件路径
            String filePath = f.getPath();
            // 获取文件内容
            String  fileContent = FileUtils.readFileToString(f, "utf-8");
            // 获取文件大小
            long fileSize = FileUtils.sizeOf(f);
            // 参数1:域的名称,参数2:域的内容,参数3:是否存储
            Field fieldName = new TextField("fileName", fileName, Field.Store.YES);
            Field fieldPath = new TextField("filePath", filePath, Field.Store.YES);
            Field fieldContent = new TextField("fileContent", fileContent, Field.Store.YES);
            Field fieldSize = new TextField("fileSize", String.valueOf(fileSize), Field.Store.YES);
    
            // 4. 创建文档对象
            Document doc = new Document();
            // 向文档对象中添加索引库
            doc.add(fieldName);
            doc.add(fieldPath);
            doc.add(fieldContent);
            doc.add(fieldSize);
    
            // 5. 把文档对象写入索引库
            indexWriter.addDocument(doc);
        }
        // 6. 关闭 IndexWriter 对象
        indexWriter.close();
    }
    

注:可以使用 luke 查看索引库中的内容。

3.3 查询索引库

  1. 创建一个 Directory 对象,指定索引库的位置;
  2. 创建一个 IndexReader 对象;
  3. 创建一个 IndexSearcher 对象,构造方法中的参数为 IndexReader 对象;
  4. 创建一个 Query 对象,TermQuery;
  5. 执行查询,得到一个 TopDocs 对象;
  6. 取查询结果的总记录数;
  7. 取文档列表;
  8. 打印文档中的内容;
  9. 关闭 IndexReader 对象。
    /**
     * 2. 从索引库中查找文档
     * */
    @Test
    public void searchIndex() throws IOException {
        // 1. 创建一个 Directory 对象,指定索引库的位置
        Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath());
        // 2. 创建一个 IndexReader 对象
        IndexReader indexReader = DirectoryReader.open(directory);
        // 3. 创建一个 IndexSearcher 对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 4. 创建一个 Query 对象,TermQuery
        Query query = new TermQuery(new Term("fileContent", "spring"));
        // 5. 执行查询,得到一个TopDocs对象
        // 传入查询条件并设置最多返回 10 条结果
        TopDocs topDocs = indexSearcher.search(query, 10);
        System.out.println("总的记录条数为:" + topDocs.totalHits);
        // 6. 获取文档列表
        ScoreDoc[] docs = topDocs.scoreDocs;
        // 7. 打印文档中的内容
        for (ScoreDoc d :
             docs ) {
            // 获取文档的 id
            int doc = d.doc;
            Document document = indexSearcher.doc(doc);
            System.out.println(document.get("fileName"));
            System.out.println(document.get("filePath"));
            System.out.println(document.get("fileSize"));
            System.out.println("-------------------------------");
        }
        // 8. 关闭 indexReader 对象
        indexReader.close();
    }

四、 分析器

默认情况下使用的是标准分析器 StandardAnalyzer,但是 StandardAnalyzer 对中文分词的支持不是很友好。为了实现对中文的友好支持,可以使用 IKAnalyzer 类。

  1. 查看标准分析器 StandardAnalyzer 的分析效果

    使用 Analyzer 对象的 tokenStream 方法返回一个 tokenStream 对象。此对象中包含了最终的分词结果。

    实现步骤:

    1. 创建一个 Analyzer 对象,StandardAnalyzer 对象;
    2. 使用分析器对象的 tokenStream 方法获得一个 TokenStream 对象;
    3. 向 TokenStream 对象中设置一个引用,相当于是一个指针;
    4. 调用 TokenStream 对象的 reset 方法,如果不调用则会抛出异常;
    5. 使用 while 循环遍历 TokenStream 对象;
    6. 关闭 TokenStream 对象。

    代码演示:

    /**
     * 3. 测试使用 Analyzer
     * */
    @Test
    public void testIKAnalyzer() throws IOException {
        // 1. 创建一个Analyzer对象,StandardAnalyzer 对象,主要是对英文进行分析
        // Analyzer analyzer = new StandardAnalyzer();
        // IKAnalyzer 主要是对中文进行分析
        IKAnalyzer analyzer = new IKAnalyzer();
        // 2. 使用分析器对象的 tokenStream 方法获得一个 TokenStream 对象
        // TokenStream tokenStream = analyzer.tokenStream("", "Learn how to create a web page with Spring MVC.");
        TokenStream tokenStream = analyzer.tokenStream("","今天是个好日子啊,真的是个好日子啊,白日依山尽");
        // 3. 向 TokenStream 对象中设置一个引用,相当于是一个指针
        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();
    }
    
  2. IKAnalyzer 的使用方法

    1. 把 IKAnalyzer 的 jar 包添加到工程中;

       * IK-Analyzer-1.0-SNAPSHOT.jar
      
    2. 把配置文件和扩展词典添加到工程的 classpath 下。

       * hotword.dic
       * IKAnalyzer.cfg.xml
       * stopword.dic
      

    注意:扩展词严禁使用 windows 自带的记事本编辑,主要是为了保证编码格式为 “UTF-8”。

    • 扩展词典:添加一些新词;
    • 停用词词典:无意义的词或者是敏感词汇。

    代码演示:

     // 创建 写索引 对象时,把 IKAnalyzer 对象添加进去配置对象即可
     IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
    

五、 索引库维护

  1. 添加文档

    /**
     * 1. 添加文档
     * */
    @Test
    public void addDocument() throws IOException {
        Document document = new Document();
        document.add(new TextField("name", "文章标题", Field.Store.YES));
        // 是否存储的标准:是否要将内容展示给用户
        document.add(new TextField("content", "这是文章的内容哦,弟中弟", Field.Store.NO));
        // 不分析,不索引,但要Field存储在文档中
        document.add(new StoredField("path", "c:/temp/hello"));
    
        // 把 document 对象存储到索引域中
        indexWriter.addDocument(document);
    
        indexWriter.close();
    }
    
  2. 删除文档

    • 删除全部文档

      /**
       * 2. 删除所有文档
       * */
      @Test
      public void deleteAllDocuments() throws IOException {
          indexWriter.deleteAll();
          indexWriter.close();
      }
      
    • 根据查询、关键词查询文档

      /**
       * 3. 删除指定文档
       * */
      @Test
      public void deleteDocumentsByTerm() throws Exception{
          // 删除域名为 name 域中 包含 “文章” 关键字的文档
          indexWriter.deleteDocuments(new Term("name", "文章"));
          indexWriter.close();
      }
      
  3. 更新文档

    /**
     * 4. 更新文档
     * */
    @Test
    public void updateDocument() throws Exception{
        Document document = new Document();
        document.add(new TextField("name", "更新后,这是第一个标题", Field.Store.YES));
        document.add(new TextField("name1", "更新后,哈哈,介系第二个嘛", Field.Store.YES));
        document.add(new TextField("name2", "这是更新后的第三个标题", Field.Store.YES));
        indexWriter.updateDocument(new Term("name", "标题"), document);
    
        indexWriter.close();
    }
    

六、索引库查询

  1. 使用 Query 的子类

    1. TermQuery

      根据关键词进行查询,需要指定要查询的域以及关键词。

       public void testTermQuery() throws IOException {
          // 1. 创建一个 Directory 对象,指定索引库的位置
          Directory directory = FSDirectory.open(new File("E:\\java\\15_Lucene_IndexStore").toPath());
          // 2. 创建一个 IndexReader 对象
          IndexReader indexReader = DirectoryReader.open(directory);
          // 3. 创建一个 IndexSearcher 对象
          IndexSearcher indexSearcher = new IndexSearcher(indexReader);
          // 4. 创建一个 Query 对象,TermQuery
          Query query = new TermQuery(new Term("fileContent", "spring"));
          // 5. 执行查询,得到一个TopDocs对象
          // 传入查询条件并设置最多返回 10 条结果
          TopDocs topDocs = indexSearcher.search(query, 10);
          System.out.println("总的记录条数为:" + topDocs.totalHits);
          // 6. 获取文档列表
          ScoreDoc[] docs = topDocs.scoreDocs;
          // 7. 打印文档中的内容
          for (ScoreDoc d :
               docs ) {
              // 获取文档的 id
              int doc = d.doc;
              Document document = indexSearcher.doc(doc);
              System.out.println(document.get("fileName"));
              System.out.println(document.get("filePath"));
              System.out.println(document.get("fileSize"));
              System.out.println("-------------------------------");
          }
          // 8. 关闭 indexReader 对象
          indexReader.close();
      }
      
      
    2. RangeQuery

      范围查询。

      代码演示:

      /**
       * 限定文件大小范围查找
       * */
      @Test
      public void testRangeQuery() throws Exception{
          // 查找条件为:域名为 size,且文件大小在 1~1000 字节之间
          Query query = LongPoint.newRangeQuery("fileSize", 1, 1000);
      	// 自己写的查询方法,具体怎么写,自己去上面查琢磨琢磨
          printQuery(query);
      }
      
  2. 使用 QueryParser 进行查询

    可以先对要查询的内容先分词,然后基于分词的结果进行查询。

    需要添加如下 jar 包:

     * lucene-queryparser-7.4.0.jar
    

    代码演示:

    /**
     * 对搜索的内容进行分析,然后查找
     * */
    @Test
    public void testQueryParser () throws Exception{
        // 创建 queryParser 对象:要搜索哪个域,要对搜索的词使用什么分析器
        QueryParser queryParser = new QueryParser("fileName", new IKAnalyzer());
        // 要搜索的内容,并且对内容进行分析
        Query query =  queryParser.parse("lucene是一个Java开发的全文检索工具包");
        // 执行查询
        printQuery(query);
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值