Lucene5学习之自定义Collector

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

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

Java代码  Lucene5学习之自定义Collector
  1. /** 
  2.  * <p>Expert: Collectors are primarily meant to be used to 
  3.  * gather raw results from a search, and implement sorting 
  4.  * or custom result filtering, collation, etc. </p> 
  5.  * 
  6.  * <p>Lucene's core collectors are derived from {@link Collector} 
  7.  * and {@link SimpleCollector}. Likely your application can 
  8.  * use one of these classes, or subclass {@link TopDocsCollector}, 
  9.  * instead of implementing Collector directly: 
  10.  * 
  11.  * <ul> 
  12.  * 
  13.  *   <li>{@link TopDocsCollector} is an abstract base class 
  14.  *   that assumes you will retrieve the top N docs, 
  15.  *   according to some criteria, after collection is 
  16.  *   done.  </li> 
  17.  * 
  18.  *   <li>{@link TopScoreDocCollector} is a concrete subclass 
  19.  *   {@link TopDocsCollector} and sorts according to score + 
  20.  *   docID.  This is used internally by the {@link 
  21.  *   IndexSearcher} search methods that do not take an 
  22.  *   explicit {@link Sort}. It is likely the most frequently 
  23.  *   used collector.</li> 
  24.  * 
  25.  *   <li>{@link TopFieldCollector} subclasses {@link 
  26.  *   TopDocsCollector} and sorts according to a specified 
  27.  *   {@link Sort} object (sort by field).  This is used 
  28.  *   internally by the {@link IndexSearcher} search methods 
  29.  *   that take an explicit {@link Sort}. 
  30.  * 
  31.  *   <li>{@link TimeLimitingCollector}, which wraps any other 
  32.  *   Collector and aborts the search if it's taken too much 
  33.  *   time.</li> 
  34.  * 
  35.  *   <li>{@link PositiveScoresOnlyCollector} wraps any other 
  36.  *   Collector and prevents collection of hits whose score 
  37.  *   is &lt;= 0.0</li> 
  38.  * 
  39.  * </ul> 
  40.  * 
  41.  * @lucene.experimental 
  42.  */  
  43. public interface Collector {  
  44.   
  45.   /** 
  46.    * Create a new {@link LeafCollector collector} to collect the given context. 
  47.    * 
  48.    * @param context 
  49.    *          next atomic reader context 
  50.    */  
  51.   LeafCollector getLeafCollector(LeafReaderContext context) throws IOException;  
  52.   
  53. }  

    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接口只有一个接口方法:

Java代码  Lucene5学习之自定义Collector
  1. LeafCollector getLeafCollector(LeafReaderContext context) throws IOException;  

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

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

    接着看看LeafCollector的源码说明:

Java代码  Lucene5学习之自定义Collector
  1. /** 
  2.  * <p>Collector decouples the score from the collected doc: 
  3.  * the score computation is skipped entirely if it's not 
  4.  * needed.  Collectors that do need the score should 
  5.  * implement the {@link #setScorer} method, to hold onto the 
  6.  * passed {@link Scorer} instance, and call {@link 
  7.  * Scorer#score()} within the collect method to compute the 
  8.  * current hit's score.  If your collector may request the 
  9.  * score for a single hit multiple times, you should use 
  10.  * {@link ScoreCachingWrappingScorer}. </p> 
  11.  *  
  12.  * <p><b>NOTE:</b> The doc that is passed to the collect 
  13.  * method is relative to the current reader. If your 
  14.  * collector needs to resolve this to the docID space of the 
  15.  * Multi*Reader, you must re-base it by recording the 
  16.  * docBase from the most recent setNextReader call.  Here's 
  17.  * a simple example showing how to collect docIDs into a 
  18.  * BitSet:</p> 
  19.  *  
  20.  * <pre class="prettyprint"> 
  21.  * IndexSearcher searcher = new IndexSearcher(indexReader); 
  22.  * final BitSet bits = new BitSet(indexReader.maxDoc()); 
  23.  * searcher.search(query, new Collector() { 
  24.  * 
  25.  *   public LeafCollector getLeafCollector(LeafReaderContext context) 
  26.  *       throws IOException { 
  27.  *     final int docBase = context.docBase; 
  28.  *     return new LeafCollector() { 
  29.  * 
  30.  *       <em>// ignore scorer</em> 
  31.  *       public void setScorer(Scorer scorer) throws IOException { 
  32.  *       } 
  33.  * 
  34.  *       public void collect(int doc) throws IOException { 
  35.  *         bits.set(docBase + doc); 
  36.  *       } 
  37.  * 
  38.  *     }; 
  39.  *   } 
  40.  * 
  41.  * }); 
  42.  * </pre> 
  43.  * 
  44.  * <p>Not all collectors will need to rebase the docID.  For 
  45.  * example, a collector that simply counts the total number 
  46.  * of hits would skip it.</p> 
  47.  * 
  48.  * @lucene.experimental 
  49.  */  
  50. public interface LeafCollector {  
  51.   
  52.   /** 
  53.    * Called before successive calls to {@link #collect(int)}. Implementations 
  54.    * that need the score of the current document (passed-in to 
  55.    * {@link #collect(int)}), should save the passed-in Scorer and call 
  56.    * scorer.score() when needed. 
  57.    */  
  58.   void setScorer(Scorer scorer) throws IOException;  
  59.     
  60.   /** 
  61.    * Called once for every document matching a query, with the unbased document 
  62.    * number. 
  63.    * <p>Note: The collection of the current segment can be terminated by throwing 
  64.    * a {@link CollectionTerminatedException}. In this case, the last docs of the 
  65.    * current {@link org.apache.lucene.index.LeafReaderContext} will be skipped and {@link IndexSearcher} 
  66.    * will swallow the exception and continue collection with the next leaf. 
  67.    * <p> 
  68.    * Note: This is called in an inner search loop. For good search performance, 
  69.    * implementations of this method should not call {@link IndexSearcher#doc(int)} or 
  70.    * {@link org.apache.lucene.index.IndexReader#document(int)} on every hit. 
  71.    * Doing so can slow searches by an order of magnitude or more. 
  72.    */  
  73.   void collect(int doc) throws IOException;  
  74.   
  75. }  

    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。

Java代码  Lucene5学习之自定义Collector
  1. void collect(int doc) throws IOException;  

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

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

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

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

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

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

Java代码  Lucene5学习之自定义Collector
  1. package com.yida.framework.lucene5.collector;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.ArrayList;  
  5. import java.util.List;  
  6.   
  7. import org.apache.lucene.index.LeafReaderContext;  
  8. import org.apache.lucene.index.SortedDocValues;  
  9. import org.apache.lucene.search.Collector;  
  10. import org.apache.lucene.search.LeafCollector;  
  11. import org.apache.lucene.search.ScoreDoc;  
  12. import org.apache.lucene.search.Scorer;  
  13. /** 
  14.  * 自定义Collector结果收集器 
  15.  * @author Lanxiaowei 
  16.  * 
  17.  */  
  18. public class GroupCollector implements Collector, LeafCollector {  
  19.     /**评分计算器*/  
  20.     private Scorer scorer;  
  21.     /**段文件的编号*/  
  22.     private int docBase;  
  23.       
  24.     private String fieldName;  
  25.     private SortedDocValues sortedDocValues;  
  26.       
  27.     private List<ScoreDoc> scoreDocs = new ArrayList<ScoreDoc>();  
  28.       
  29.     public LeafCollector getLeafCollector(LeafReaderContext context)  
  30.             throws IOException {  
  31.         this.sortedDocValues = context.reader().getSortedDocValues(fieldName);  
  32.         return this;  
  33.     }  
  34.       
  35.     public void setScorer(Scorer scorer) throws IOException {  
  36.         this.scorer = scorer;  
  37.     }  
  38.   
  39.     public void collect(int doc) throws IOException {  
  40.         // scoreDoc:docId和评分  
  41.         this.scoreDocs.add(new ScoreDoc(this.docBase + doc, this.scorer.score()));  
  42.     }  
  43.   
  44.     public GroupCollector(String fieldName) {  
  45.         super();  
  46.         this.fieldName = fieldName;  
  47.     }  
  48.   
  49.     public int getDocBase() {  
  50.         return docBase;  
  51.     }  
  52.   
  53.     public void setDocBase(int docBase) {  
  54.         this.docBase = docBase;  
  55.     }  
  56.   
  57.     public String getFieldName() {  
  58.         return fieldName;  
  59.     }  
  60.   
  61.     public void setFieldName(String fieldName) {  
  62.         this.fieldName = fieldName;  
  63.     }  
  64.   
  65.     public SortedDocValues getSortedDocValues() {  
  66.         return sortedDocValues;  
  67.     }  
  68.   
  69.     public void setSortedDocValues(SortedDocValues sortedDocValues) {  
  70.         this.sortedDocValues = sortedDocValues;  
  71.     }  
  72.   
  73.     public List<ScoreDoc> getScoreDocs() {  
  74.         return scoreDocs;  
  75.     }  
  76.   
  77.     public void setScoreDocs(List<ScoreDoc> scoreDocs) {  
  78.         this.scoreDocs = scoreDocs;  
  79.     }  
  80.   
  81.     public Scorer getScorer() {  
  82.         return scorer;  
  83.     }  
  84. }  

    

Java代码  Lucene5学习之自定义Collector
  1. package com.yida.framework.lucene5.collector;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5. import java.util.List;  
  6.   
  7. import org.apache.lucene.document.Document;  
  8. import org.apache.lucene.index.DirectoryReader;  
  9. import org.apache.lucene.index.IndexReader;  
  10. import org.apache.lucene.index.Term;  
  11. import org.apache.lucene.search.IndexSearcher;  
  12. import org.apache.lucene.search.ScoreDoc;  
  13. import org.apache.lucene.search.TermQuery;  
  14. import org.apache.lucene.store.Directory;  
  15. import org.apache.lucene.store.FSDirectory;  
  16. /** 
  17.  * 自定义Collector测试 
  18.  * @author Lanxiaowei 
  19.  * 
  20.  */  
  21. public class GroupCollectorTest {  
  22.     public static void main(String[] args) throws IOException {  
  23.         String indexDir = "C:/lucenedir";  
  24.         Directory directory = FSDirectory.open(Paths.get(indexDir));  
  25.         IndexReader reader = DirectoryReader.open(directory);  
  26.         IndexSearcher searcher = new IndexSearcher(reader);  
  27.         TermQuery termQuery = new TermQuery(new Term("title""lucene"));  
  28.         GroupCollector collector = new GroupCollector("title2");  
  29.         searcher.search(termQuery, null, collector);  
  30.         List<ScoreDoc> docs = collector.getScoreDocs();  
  31.         for (ScoreDoc scoreDoc : docs) {  
  32.             int docID = scoreDoc.doc;  
  33.             Document document = searcher.doc(docID);  
  34.             String title = document.get("title");  
  35.             float score = scoreDoc.score;  
  36.             System.out.println(docID + ":" + title + "  " + score);  
  37.         }  
  38.           
  39.         reader.close();  
  40.         directory.close();  
  41.     }  
  42. }  

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

Java代码  Lucene5学习之自定义Collector
  1. protected TopFieldDocs search(Weight weight, FieldDoc after, int nDocs,  
  2.                                 Sort sort, boolean fillFields,  
  3.                                 boolean doDocScores, boolean doMaxScore)  
  4.       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技术群一起交流学习。

    群号: 
Lucene5学习之自定义Collector

转载:http://iamyida.iteye.com/blog/2202111