Lucene in Action-为应用程序添加搜索功能

谁说我不能喝 提交于 2019-12-02 05:48:36

QueryParser 类的使用

    • matchVersion 值的是 版本(为了兼容)
    • 解析表达式
    • 不成功抛出 ParseException
    • 多个项时,默认是OR

IndexSearcher

  • IndexReader 打开索引文件、提供底层redaer API 的繁重工作
    • 需要较大系统开销
    • 在搜索其间最好打开一个大家公用就好
    • 需要限制其打开的频率(可以类似 数据库 connection处理)
      • reopen 消耗较少的系统资源
      • 如果索引变更,会返回新的reader ,和旧的不一样 
        • 此时依然有很多程序在使用旧的reader ,请保证线程安全
        • 如下所示,如果不一样,想看到新的索引,需要创建新的IndexSearcher

实现搜索功能

  • search() 方法
    • 其他高级搜索包括过滤和排序

使用TopDocs 类

  • TopDocs.totalHits
  • TopDocs.scoreDocs:匹配的顶部文档数组

搜索结果分页

  • 分页两种实现:
      • 重新查询往往更好,Lucene的高性能查询弥补了其浪费资源的短板
        • 操作系统的I/O缓存机制,使得重新查询很快结束

近实时搜索

  • 不必关闭writer、再向该writer 提交
  • 长期打开一个IndexWriter 完成持续变更
    • 允许对新创建的、还未提交的段进行搜索
  • public class NearRealTimeTest extends TestCase {
      public void testNearRealTime() throws Exception {
        Directory dir = new RAMDirectory();
        IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(Version.LUCENE_30), IndexWriter.MaxFieldLength.UNLIMITED);
        for(int i=0;i<10;i++) {
          Document doc = new Document();
          doc.add(new Field("id", ""+i, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
          doc.add(new Field("text", "aaa", Field.Store.NO, Field.Index.ANALYZED));
          writer.addDocument(doc);
        }
       // 会将缓存中所有变更,刷新到索引目录,返回一个包含这些变更的新的reader
        IndexReader reader = writer.getReader();                 // #1  创建近实时 reader
    
        IndexSearcher searcher = new IndexSearcher(reader);      // #A
    
        Query query = new TermQuery(new Term("text", "aaa"));
        TopDocs docs = searcher.search(query, 1);
        assertEquals(10, docs.totalHits);                        // #B
    
        writer.deleteDocuments(new Term("id", "7"));             // #2
    
        Document doc = new Document();                           // #3
        doc.add(new Field("id",                                  // #3
                          "11",                                  // #3
                          Field.Store.NO,                        // #3
                          Field.Index.NOT_ANALYZED_NO_NORMS));   // #3
        doc.add(new Field("text",                                // #3
                          "bbb",                                 // #3
                          Field.Store.NO,                        // #3
                          Field.Index.ANALYZED));                // #3
        writer.addDocument(doc);                                 // #3
        
        // reopen 完成更多变更,效率很高:
                                  // 只会打开上次open、reopen的文件
                                  // 未发生任何改变的被共享(共享先前的reader)                         
        IndexReader newReader = reader.reopen();                 // #4
        assertFalse(reader == newReader);                        // #5
        reader.close();                                          // #6
        searcher = new IndexSearcher(newReader);              
    
        TopDocs hits = searcher.search(query, 10);               // #7
        assertEquals(9, hits.totalHits);                         // #7
    
        query = new TermQuery(new Term("text", "bbb"));          // #8
        hits = searcher.search(query, 1);                        // #8
        assertEquals(1, hits.totalHits);                         // #8
    
        newReader.close();
        writer.close();
      }
    }
    
    /*
      #1 Create near-real-time reader
      #A Wrap reader in IndexSearcher
      #B Search returns 10 hits
      #2 Delete 1 document
      #3 Add 1 document
      #4 Reopen reader
      #5 Confirm reader is new
      #6 Close old reader
      #7 Verify 9 hits now
      #8 Confirm new document matched
    */

理解Lucene的 评分机制

  • 评分公式(详见之前博客的推导公式):

explain() 方法理解搜索结果评分:

  • 开销和查询是一样的,不要过度使用
  • public class Explainer {
      public static void main(String[] args) throws Exception {
        if (args.length != 2) {
          System.err.println("Usage: Explainer <index dir> <query>");
          System.exit(1);
        }
    
        String indexDir = args[0];
        String queryExpression = args[1];
    
        Directory directory = FSDirectory.open(new File(indexDir));
        QueryParser parser = new QueryParser(Version.LUCENE_30,
                                             "contents", new SimpleAnalyzer());
        Query query = parser.parse(queryExpression);
    
        System.out.println("Query: " + queryExpression);
    
        IndexSearcher searcher = new IndexSearcher(directory);
        TopDocs topDocs = searcher.search(query, 10);
    
        for (ScoreDoc match : topDocs.scoreDocs) {
          Explanation explanation
             = searcher.explain(query, match.doc);     //#A
    
          System.out.println("----------");
          Document doc = searcher.doc(match.doc);
          System.out.println(doc.get("title"));
          System.out.println(explanation.toString());  //#B
        }
        searcher.close();
        directory.close();
      }
    }

     

Lucene 多样化查询:

  • 详见之前博客内容

解析查询表达式:QueryParser

  • 需要转义的字符:
  • Query.toString()
    • 查看 QueryParser 对象解析后的样子

TermQuery

    • 将 查询自动解析为 输出的样子

项范围查询:

  • [] 边界值包含在内,{} 不包含在内

通配符查询仅仅在末尾有一个*

  • 会被优化为前缀查询
  • 二者都会转化大小写,不过能人为设置不转化

布尔查询::

  • AND OR NOT 必须全部大写
  • 默认是OR (a  b   默认认为是 a OR b)
  • 可修改默认:
  • NOT 不能单独使用

短语查询:

  • 双引号括起来的项,转化为短语查询
    • “this is tom cat*”  
      • 分析结果 tom  cat   
        • 注意 前俩是停用词
        • 最后的* 在双引号内不考虑
      • 不设置slop 默认值为0
    • SpanNearQuery 确保按照顺序
    • PhraseQuery 松散的,不一定按照顺序

模糊查询:

  • ~0.7 指定最小相似 程度

MatchAllDocsQuery

  • *:*   会被解析为 MatchAllDocsQuery

分组查询:

  • 小括号,来建立子查询

域选择:

  • 要求用户知道被搜索的域是不太友善的
    • 默认的域在创建QueryParser 时创建
    • 也可以指定默认域为所有域
      • title:lucene 限定查询域为title
      • filed (a b c)
        • filed:a OR filed:b OR filed:c

为子查询加权

  • junit 加权为 2.0   testing 1.0

是否一定使用QueryParser

  • QueryParser 提供了快捷强大的查询构建
    • 不能适合所有的场合
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!