Lucene (4) 搜索

当你查询Lucene索引时, 它将会返回一个包含有序的ScoreDoc对象数组的TopDocs对象.

在输入查询后, Lucene会为每个文档计算评分(用以表示相关性的数值)

IndexSearcher所有搜索都通过IndexSearcher进行, 它们会调用该类中重载的search方法
Query封装某种查询类型的具体子类. Query实例将被传递给IndexSearcher的search方法
QueryParser将用户输入的查询表达式处理成具体的Query对象
TopDocs保持由IndexSearcher.search()方法返回的具有较高评分的顶部文档
ScoreDoc提供对TopDocs中没跳搜索结果的访问接口
 // 3. search
    int hitsPerPage = 10;
    IndexReader reader = DirectoryReader.open(index);
    IndexSearcher searcher = new IndexSearcher(reader);
    TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage);
    searcher.search(q, collector);
    ScoreDoc[] hits = collector.topDocs().scoreDocs;
     
    // 4. display results
    System.out.println("Found " + hits.length + " hits.");
    for(int i=0;i<hits.length;++i) {
      int docId = hits[i].doc;
      Document d = searcher.doc(docId);
      System.out.println((i + 1) + ". " + d.get("isbn") + "\t" + d.get("title"));
    }
 
    // reader can only be closed when there
    // is no need to access the documents any more.
    reader.close();

Lucene的搜索方法需要一个Query对象作为参数.

我们可以通过QueryParser类对查询表达式进行解析, 它把诸如 +A +B -C 或者 A OR B 这样的多个查询条件解析成一个Query类

Query q = new QueryParser("title", analyzer).parse(querystr);

如果表达式解析不成功, Lucene会抛出一个ParseException异常.

A默认域包含A的文档
A BA OR B默认域包含A和B中的一个或两个的文档
+A +BA AND B默认域中同时包含A和B的文档
title:Atitle域中包含A项的文档
title:A -subject:Btitle:A AND NOT subject:Btitle域中包含A 并且 subject域中不包含B的文档
(A OR B) AND C默认域中包含C 并且包含 A和B中的其中一个或者两个的文档
title: “hello world”title域为hello world的文档
title: “hello world” ~5title域中hello和world之间距离小于5的文档
hello*包含由hello开头的文档, 包括hello本身
hello~包含与单词hello相近的文档,例如Jello
lastmodified:[1/1/2017 TO 12/31/2017]lastmodified域在2017年1月1日和2017年12月31日之间的文档

用Lucene进行搜索操作异常简单, 首先建立一个IndexSearcher实例, 它负责打开索引, 然后改实例的search方法即可进行搜索操作.

程序返回TopDocs对象表示顶部搜索结果

要创建IndexSearcher类的实例, 首先需要一个用于存储索引的目录

上一个例子我们使用了内存目录, 这次使用一个真实的磁盘路径

Directory dir = FSDirectory.open(Paths.get(“c:\\myindex”));

通过这个dir可以方便的创建index reader/writer

IndexWriter indexWriter = new IndexWriter(dir, indexWriterConfig);

IndexReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = new IndexSearcher(reader);

这里需要注意的是, IndexReader完成了诸如打开所有索引文件和提供底层reader API 等复杂的工作, 而 IndexSearcher 则简单的多.

由于打开一个IndexReader需要较大的系统开销, 因此最好在所有搜索期间都重复使用同一个IndexReader实例, 只有在必要的时候才建议打开新的IndexReader

在创建IndexReader时,它会搜索已有的索引快照. 如果你需要搜索索引中的变更信息, 那么必须打开新的reader.

IndexReader.reopen方法是一个获取新IndexReader的有效手段, 重启的IndexReader能在耗费较少系统资源的情况下使用当前reader来获取索引中的所有变更信息.

一旦获取了IndexSearcher实例, 就可以通过调用它的search方法来进行搜索了

search方法会快速完成大量工作. 它会访问所有候选的搜索匹配文档, 并只返回符合每个查询条件的结果.

最后它会收集最靠前的几个搜索结果并返回给调用程序.

TopDocs search(Query query, int n) 直接进行搜索. int n 参数表示返回的最高评分的文档数量
TopDocs search(Query query, Filter filter, int n)搜索受文档子集约束, 约束条件基于过滤策略
TopFieldDocs search(Query query, Filter filter, int n, Sort sort)搜索受文档子集约束, 约束条件基于过滤策略, 结果排序通过自定义的Sort完成
void search(Query query, Collector results)当使用自定义文档访问策略时使用, 或者不想以默认的前n个搜索结果培训策略收集结果时使用
void search(Query query, Filter filter, Collector results)同上. 区别在于结果文档只有在传入过滤策略时才能被接收

Topdocs.totalHits 属性会返回匹配文档数量. 在默认情况下, 匹配文档是按照评分降序排序的(DESC)

TopDocs.scoreDocs属性是一个数组, 它包含程序所要求的顶部匹配文档数量.

每个scoreDoc实例都有一个浮点类型的评分, 该评分是相关性评分, 该实例还包含一个整型数字用于标示文档ID, 能够用于检索文档中保存的域

ScoreDoc[] hits = collector.topDocs().scoreDocs;

for(int i=0;i<hits.length;++i) {
      int docId = hits[i].doc;
      Document d = searcher.doc(docId);
      System.out.println((i + 1) + ". " + d.get("filename") + "\t" + d.get("filepath"));
}
package lzlucene;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

public class HelloLucene2 {
  public static void main(String[] args) throws IOException, ParseException {
      
    // 1. create the index
    Directory dir = FSDirectory.open(Paths.get("c:\\myindex"));
    Analyzer analyzer = new StandardAnalyzer();
    IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
    IndexWriter indexWriter = new IndexWriter(dir, indexWriterConfig);
    indexWriter.deleteAll();
    
    File dFile = new File("C:/");
    File[] files = dFile.listFiles();
    for (File file : files) {
        Document document = new Document();
        document.add(new Field("filename", file.getName(), TextField.TYPE_STORED));
        document.add(new Field("filepath", file.getAbsolutePath(), TextField.TYPE_STORED));
        indexWriter.addDocument(document);

    }
 
    indexWriter.commit();
    indexWriter.close();
    
    // 2. query
    String querystr = args.length > 0 ? args[0] : "out.txt";
    Query q = new QueryParser("filename", analyzer).parse(querystr);
 
    // 3. search
    int hitsPerPage = 10;
    IndexReader reader = DirectoryReader.open(dir);
    IndexSearcher searcher = new IndexSearcher(reader);
    TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage);
    searcher.search(q, collector);
    ScoreDoc[] hits = collector.topDocs().scoreDocs;
     
    // 4. display results
    System.out.println("Found " + hits.length + " hits.");
    for(int i=0;i<hits.length;++i) {
      int docId = hits[i].doc;
      Document d = searcher.doc(docId);
      System.out.println((i + 1) + ". " + d.get("filename") + "\t" + d.get("filepath"));
    }
 
    reader.close();
  }

}