`
lxwt909
  • 浏览: 572843 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Lucene5学习之自定义Collector

阅读更多

        你们都睡了,而我却在写博客,呵呵!我也不知道为什么都夜深了,我却还没一点困意,趁着劲头赶紧把自定义结果集写完,已经拖了2天没更新了,不能让你们等太久,我也要把写博客一直坚持下去。

        Collector是什么?还是看源码吧。这也是最权威的解释说明。

/**
 * <p>Expert: Collectors are primarily meant to be used to
 * gather raw results from a search, and implement sorting
 * or custom result filtering, collation, etc. </p>
 *
 * <p>Lucene's core collectors are derived from {@link Collector}
 * and {@link SimpleCollector}. Likely your application can
 * use one of these classes, or subclass {@link TopDocsCollector},
 * instead of implementing Collector directly:
 *
 * <ul>
 *
 *   <li>{@link TopDocsCollector} is an abstract base class
 *   that assumes you will retrieve the top N docs,
 *   according to some criteria, after collection is
 *   done.  </li>
 *
 *   <li>{@link TopScoreDocCollector} is a concrete subclass
 *   {@link TopDocsCollector} and sorts according to score +
 *   docID.  This is used internally by the {@link
 *   IndexSearcher} search methods that do not take an
 *   explicit {@link Sort}. It is likely the most frequently
 *   used collector.</li>
 *
 *   <li>{@link TopFieldCollector} subclasses {@link
 *   TopDocsCollector} and sorts according to a specified
 *   {@link Sort} object (sort by field).  This is used
 *   internally by the {@link IndexSearcher} search methods
 *   that take an explicit {@link Sort}.
 *
 *   <li>{@link TimeLimitingCollector}, which wraps any other
 *   Collector and aborts the search if it's taken too much
 *   time.</li>
 *
 *   <li>{@link PositiveScoresOnlyCollector} wraps any other
 *   Collector and prevents collection of hits whose score
 *   is &lt;= 0.0</li>
 *
 * </ul>
 *
 * @lucene.experimental
 */
public interface Collector {

  /**
   * Create a new {@link LeafCollector collector} to collect the given context.
   *
   * @param context
   *          next atomic reader context
   */
  LeafCollector getLeafCollector(LeafReaderContext context) throws IOException;

}

    Collector系列接口是用来收集查询结果,实现排序,自定义结果集过滤和收集。Collector和LeafCollector是Lucene结果集收集的核心。

    TopDocsCollector:是用来收集Top N结果的,

    TopScoreDocCollector:它是TopDocsCollector的子类,它返回的结果集会根据评分和docId进行排序,该接口在IndexSearcher类的search方法内部被调用,但search方法并不需要显式的指定一个Sort排序器,TopScoreDocCollector是使用频率最高的一个结果收集器接口。

     TopFieldCollector:它也是TopDocsCollector的子类,跟TopScoreDocCollector的区别是,TopScoreDocCollector是根据评分和docId进行排序的,而TopFieldCollector是根据用户指定的域进行排序,在调用IndexSearcher.search方法时需要显式的指定Sort排序器。

      TimeLimitingCollector:它是其他Collector的包装器,它的功能是当被包装的Collector耗时超过限制时可以中断收集过程。

      PositiveScoresOnlyCollector:从类名就知道它是干嘛的,Positive正数的意思,即只返回score评分大于零的索引文档,它跟TimeLimitingCollector都属于其他Collector的包装器,都使用了装饰者模式。

 

      Collector接口只有一个接口方法:

LeafCollector getLeafCollector(LeafReaderContext context) throws IOException;

     根据提供的IndexReader上下文对象返回一个LeafCollector,LeafCollector其实就是对应每个段文件的收集器,每次切换段文件时都会调用一次此接口方法。

    其实LeafCollector才是结果收集器接口,Collector只是用来生成每个段文件对应的LeafCollector,在Lucene4,x时代,Collector和LeafCollector并没有分开,现在Lucene5.0中,接口定义粒度更细了,为用户自定义扩展提供了更多的便利。

    接着看看LeafCollector的源码说明:

/**
 * <p>Collector decouples the score from the collected doc:
 * the score computation is skipped entirely if it's not
 * needed.  Collectors that do need the score should
 * implement the {@link #setScorer} method, to hold onto the
 * passed {@link Scorer} instance, and call {@link
 * Scorer#score()} within the collect method to compute the
 * current hit's score.  If your collector may request the
 * score for a single hit multiple times, you should use
 * {@link ScoreCachingWrappingScorer}. </p>
 * 
 * <p><b>NOTE:</b> The doc that is passed to the collect
 * method is relative to the current reader. If your
 * collector needs to resolve this to the docID space of the
 * Multi*Reader, you must re-base it by recording the
 * docBase from the most recent setNextReader call.  Here's
 * a simple example showing how to collect docIDs into a
 * BitSet:</p>
 * 
 * <pre class="prettyprint">
 * IndexSearcher searcher = new IndexSearcher(indexReader);
 * final BitSet bits = new BitSet(indexReader.maxDoc());
 * searcher.search(query, new Collector() {
 *
 *   public LeafCollector getLeafCollector(LeafReaderContext context)
 *       throws IOException {
 *     final int docBase = context.docBase;
 *     return new LeafCollector() {
 *
 *       <em>// ignore scorer</em>
 *       public void setScorer(Scorer scorer) throws IOException {
 *       }
 *
 *       public void collect(int doc) throws IOException {
 *         bits.set(docBase + doc);
 *       }
 *
 *     };
 *   }
 *
 * });
 * </pre>
 *
 * <p>Not all collectors will need to rebase the docID.  For
 * example, a collector that simply counts the total number
 * of hits would skip it.</p>
 *
 * @lucene.experimental
 */
public interface LeafCollector {

  /**
   * Called before successive calls to {@link #collect(int)}. Implementations
   * that need the score of the current document (passed-in to
   * {@link #collect(int)}), should save the passed-in Scorer and call
   * scorer.score() when needed.
   */
  void setScorer(Scorer scorer) throws IOException;
  
  /**
   * Called once for every document matching a query, with the unbased document
   * number.
   * <p>Note: The collection of the current segment can be terminated by throwing
   * a {@link CollectionTerminatedException}. In this case, the last docs of the
   * current {@link org.apache.lucene.index.LeafReaderContext} will be skipped and {@link IndexSearcher}
   * will swallow the exception and continue collection with the next leaf.
   * <p>
   * Note: This is called in an inner search loop. For good search performance,
   * implementations of this method should not call {@link IndexSearcher#doc(int)} or
   * {@link org.apache.lucene.index.IndexReader#document(int)} on every hit.
   * Doing so can slow searches by an order of magnitude or more.
   */
  void collect(int doc) throws IOException;

}

    LeafCollector将打分操作从文档收集中分离出去了,如果你不需要打分操作,你可以完全跳过。

 如果你需要打分操作,你需要实现setScorer方法并传入一个Scorer对象,然后在collect方法中

 通过调用Scorer.score方法完成对当前命中文档的打分操作。如果你的LeafCollector在collect

 方法中需要对命中的某个索引文档调用多次score方法的话,请你使用ScoreCachingWrappingScorer

 对象包装你的Scorer对象。(利用缓存防止多次进行重复打分)

 collect方法中的doc参数是相对于当前IndexReader的,如果你需要把doc解析成docId(索引文档ID),

 你需要调用setNextReader方法来重新计算IndexReader的docBase值。

 并不是所有的Collector都需要计算docID基数的,比如对于只需要收集总的命中结果数量的Collector来说,

 可以跳过这个操作。

 

       通过以上的理解,我们可以总结出:通过Collector接口生产LeafCollector,然后通过LeafCollector接口

去完成结果收集和命中结果的打分操作。即底下真正干活的是LeafCollector。

void collect(int doc) throws IOException;

    这里collect方法用来收集每个索引文档,提供的doc参数表示段文件编号,如果你要获取索引文档的编号,请加上当前段文件Reader的docBase基数,如leafReaderContext.reader().docBase + doc;

    如果你需要自定义打分器,请继承实现自己的Scorer,那这个setScorer什么时候调用呢,这个通过阅读IndexSearcher的search方法顺藤摸瓜从而知晓,看图:


      其实内部是先把Query对象包装成Filter,然后通过调用createNormalizedWeight方法生成Weight(权重类),观摩Weight接口你会发现,其中有个Scorer scorer接口方法:


      至此我们就弄清楚了,我们的LeafCollector不用关心Scorer是怎么创建并传入到LeafCollector中的,我们只需要实现自己的Scorer即可,我们在IndexSearcher.search方法时内部会首先创建Weight,通过Weight来生成Scorer,我们在调用search方法时需要传入collector接口,那自然scorer接口就被传入了leafCollector中。

      如果实现了自己的Scorer则必然需要也要实现自己的Weight并通过自定义Weight来生成自定义Scorer,特此提醒,为了简便起见,这里就没有自定义Scorer。

     下面是一个自定义Collector的简单示例,希望能抛砖引玉,为大家排忧解惑,如果代码有任何BUG或纰漏,还望大家告知我。

package com.yida.framework.lucene5.collector;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.LeafCollector;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Scorer;
/**
 * 自定义Collector结果收集器
 * @author Lanxiaowei
 *
 */
public class GroupCollector implements Collector, LeafCollector {
	/**评分计算器*/
	private Scorer scorer;
	/**段文件的编号*/
    private int docBase;
    
    private String fieldName;
    private SortedDocValues sortedDocValues;
    
    private List<ScoreDoc> scoreDocs = new ArrayList<ScoreDoc>();
    
    public LeafCollector getLeafCollector(LeafReaderContext context)
			throws IOException {
    	this.sortedDocValues = context.reader().getSortedDocValues(fieldName);
    	return this;
	}
    
	public void setScorer(Scorer scorer) throws IOException {
		this.scorer = scorer;
	}

	public void collect(int doc) throws IOException {
        // scoreDoc:docId和评分
        this.scoreDocs.add(new ScoreDoc(this.docBase + doc, this.scorer.score()));
	}

	public GroupCollector(String fieldName) {
		super();
		this.fieldName = fieldName;
	}

	public int getDocBase() {
		return docBase;
	}

	public void setDocBase(int docBase) {
		this.docBase = docBase;
	}

	public String getFieldName() {
		return fieldName;
	}

	public void setFieldName(String fieldName) {
		this.fieldName = fieldName;
	}

	public SortedDocValues getSortedDocValues() {
		return sortedDocValues;
	}

	public void setSortedDocValues(SortedDocValues sortedDocValues) {
		this.sortedDocValues = sortedDocValues;
	}

	public List<ScoreDoc> getScoreDocs() {
		return scoreDocs;
	}

	public void setScoreDocs(List<ScoreDoc> scoreDocs) {
		this.scoreDocs = scoreDocs;
	}

	public Scorer getScorer() {
		return scorer;
	}
}

    

package com.yida.framework.lucene5.collector;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
/**
 * 自定义Collector测试
 * @author Lanxiaowei
 *
 */
public class GroupCollectorTest {
	public static void main(String[] args) throws IOException {
		String indexDir = "C:/lucenedir";
		Directory directory = FSDirectory.open(Paths.get(indexDir));
	    IndexReader reader = DirectoryReader.open(directory);
	    IndexSearcher searcher = new IndexSearcher(reader);
	    TermQuery termQuery = new TermQuery(new Term("title", "lucene"));
	    GroupCollector collector = new GroupCollector("title2");
	    searcher.search(termQuery, null, collector);
	    List<ScoreDoc> docs = collector.getScoreDocs();
		for (ScoreDoc scoreDoc : docs) {
			int docID = scoreDoc.doc;
			Document document = searcher.doc(docID);
			String title = document.get("title");
			float score = scoreDoc.score;
			System.out.println(docID + ":" + title + "  " + score);
		}
	    
	    reader.close();
	    directory.close();
	}
}

    这里仅仅是一个简单的示例,如果你需要更严格的干预索引文档,请在collect方法里实现的代码逻辑,如果你需要更细粒度的干预文档打分过程,请继承Scorer抽象类自定义的实现并继承Weight抽象类自定义的实现,然后调用IndexSearch的这个方法即可:

protected TopFieldDocs search(Weight weight, FieldDoc after, int nDocs,
                                Sort sort, boolean fillFields,
                                boolean doDocScores, boolean doMaxScore)
      throws IOException

 

     一如既往的,demo源码会上传到底下的附件里,至于有童鞋要求我的demo不要使用Maven构建,I am very sorry,I can't meet your requirments.如果你不会Maven,还是花时间去学下吧。OK,凌晨一点多了,我该搁笔就寝咯!

       哥的QQ: 7-3-6-0-3-1-3-0-5,欢迎加入哥的Java技术群一起交流学习。

    群号: 

 

  • 大小: 315.3 KB
  • 大小: 479.9 KB
4
0
分享到:
评论
1 楼 kimibaby1990 2017-08-23  
谢谢分享,学到很多东西~
有两处觉的有点小问题:
1.
>  你需要调用setNextReader方法来重新计算IndexReader的docBase值。
lucene5里setNextReader 好像改成了doSetNextReader().

2.
>  如果你要获取索引文档的编号,请加上当前段文件Reader的docBase基数,如
>  leafReaderContext.reader().docBase + doc;
lucene5里 docBase直接通过LeafReaderContext的成员docBase就能得到。

相关推荐

    lucene collector的使用

    这就是自定义Collector的用武之地。你可以根据具体需求创建自己的Collector,例如,实现分组统计。 分组统计在信息检索中是一种常见的需求,它允许我们按照某些字段(如类别、日期等)对结果进行分类,以便更好地...

    Lucene collector-开源

    源代码通常按照模块组织,可能包括新的 Collector 类和其他辅助类,开发者可以通过阅读和学习这些源码来深入理解 Lucene 收集器的工作原理,并根据自己的需求进行定制。 在实际应用中,使用这些扩展的 Lucene 收集...

    java Lucene 中自定义排序的实现

    Lucene中的自定义排序功能和Java集合中的自定义排序的实现方法差不多,都要实现一下比较接口. 在Java中只要实现Comparable接口就可以了.但是在Lucene中要实现SortComparatorSource接口和ScoreDocComparator接口.在...

    lucene 3.5学习笔记

    8. **Collector**:收集搜索结果的接口,允许自定义结果处理逻辑。 四、使用流程 1. **创建索引**:初始化Analyzer,使用IndexWriter创建或打开索引目录,添加Document到索引。 2. **查询索引**:使用QueryParser...

    Lucene资料大全(包括Lucene_in_Action书等)

    5. **过滤与聚合**:Filter和Collector组件可以用于进一步筛选结果,或者进行分组、统计等聚合操作。 6. **更新与删除**:Lucene支持动态索引更新,可以添加、修改或删除文档,并实时反映在搜索结果中。 7. **多...

    Lucene group by ,分组实现

    在 Lucene 中,分组功能并不是内建的,但可以通过自定义 collector 来实现。`GroupCollector` 就是这样一个关键组件,它是实现分组搜索的核心类。`GroupCollector` 负责收集在搜索过程中遇到的文档,并根据指定的...

    lucene.net 2.9.1 源码

    5. 响应处理:了解HitCollector和Collector的用法,以及如何自定义结果集的处理逻辑。 四、实战开发 在实际项目中,开发者可以利用Lucene.NET 2.9.1源码进行以下操作: 1. 定制Analyzer:根据特定语言或业务需求,...

    lucene5.3.1增删改查

    3. **执行查询**: 使用IndexSearcher的search(Query, Collector)方法,Collector用于收集查询结果。 4. **获取结果**: ScoreDoc数组包含了查询结果的排序信息,可以使用HitQueue或TopDocs来获取前n个最相关的结果。...

    Lucene3.0.1 官方api

    `Collector`接口则允许自定义结果收集逻辑,实现如分组、聚合等功能。 8. **高亮显示**:Lucene提供了`Highlighter`类,用于在搜索结果中突出显示匹配的关键词,提高用户体验。 9. **多线程支持**:Lucene 3.0.1...

    lucene-初级学习资料.ppt

    在学习 Lucene 时,你需要掌握以下几个关键点: - **分析器(Analyzer)**:分析器是 Lucene 处理文本的关键组件,它负责将文本分割成单词(分词),去除停用词,进行词形还原等预处理工作。不同的语言和应用场景...

    Lucene in Action 配套源码

    5. **QueryParser**:Lucene提供了一个强大的查询解析器,能够将用户的查询字符串转化为内部表示,支持各种查询语法,如布尔操作符(AND、OR、NOT)、短语查询、范围查询等。 6. **索引优化(Merge)**:为了提高...

    lucene 站内搜索示例

    默认的 `StandardAnalyzer` 可处理大部分情况,但对于特定的语言或领域,可能需要自定义分析器。分词后,使用 `Document` 对象表示每篇文档,添加字段如URL、标题、内容等。接着,使用 `IndexWriter` 创建索引,将...

    Lucene的一个毕业设计

    - **过滤器与集合器**:Filter 类可以用来限制搜索结果,而 Collector 类可以自定义结果收集行为,比如只获取前 N 个结果。 - **多字段搜索**:可以通过设置多个字段查询,使搜索更加灵活。 **总结** Lucene 的...

    lucene api

    12. **自定义扩展**:Lucene的灵活性允许开发者根据需要自定义Analyzer、Filter、Sorter等组件,以适应特定的搜索需求。 总结来说,Lucene API是一个强大的工具,为开发人员提供了构建高效、灵活的全文搜索引擎的...

    lucene-4.2.1-src.tgz

    7. **高级功能**:Lucene还包括多线程支持、近实时搜索、自定义排序、过滤器(`Filter`)和拦截器(`Collector`)等高级特性,允许开发者进行更复杂的检索策略设计。 8. **扩展性**:Lucene本身只是一个库,开发者...

    lucene-4.6.0官方文档

    这个文档集成了Lucene的核心概念、API使用、最佳实践以及常见问题解答,对于开发者来说是理解和掌握Lucene不可或缺的学习材料。 1. **Lucene基础** Lucene的核心功能包括文本分析、索引构建、搜索和结果排序。文本...

    Lucene5.3.1相关jar包

    9. **Collector**: 在搜索过程中收集匹配的文档,可以自定义实现来满足特定需求,如排序、分页等。 10. **Highlighter**: 提供搜索结果的高亮显示。 总的来说,"Lucene5.3.1相关jar包"包含了构建高效全文搜索引擎所...

    lucene-6.6.3.zip

    Lucene 提供了多种扩展点,比如自定义 Analyzer、Filter 和 Collector,以适应不同场景的需求。例如,可以通过自定义 Analyzer 实现更精确的中文分词策略,通过 Filter 进行结果过滤,通过 Collector 实现定制化的...

    lucene学习笔记

    它结合`QueryParser`或自定义`Query`对象来构造查询表达式,然后调用`search(Query, Collector)`方法获取结果。 Lucene的搜索功能强大且灵活,支持多种查询语法,包括布尔查询、短语查询、范围查询等。同时,它还...

Global site tag (gtag.js) - Google Analytics