Lucene5学习之FunctionQuery功能查询-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

Lucene5学习之FunctionQuery功能查询

简介:

    我猜,大家最大的疑问就是:不是已经有那么多Query实现类吗,为什么又设计一个FunctionQuery,它的设计初衷是什么,或者说它是用来解决什么问题的?我们还是来看看源码里是怎么解释FunctionQuery的:


        意思就是基于ValueSource来返回每个文档的评分即valueSourceScore,那ValueSource又是怎么东东?接着看看ValueSource源码里的注释说明:


 ValueSource是用来根据指定的IndexReader来实例化FunctionValues的,那FunctionValues又是啥?


         从接口中定义的函数可以了解到,FunctionValues提供了根据文档ID获取各种类型的DocValuesField域的值的方法,那这些接口返回的域值用来干嘛的,翻看FunctionQuery源码,你会发现:

 

 

       从上面几张图,我们会发现,FunctionQuery构造的时候需要提供一个ValueSource,然后在FunctionQuery的内部类AllScorer中通过valueSource实例化了FunctionValues,然后在计算FunctionQuery评分的时候通过FunctionValues获取DocValuesField的域值,域值和FunctionQuery的权重值相乘得到FunctionQuery的评分。

Java代码  收藏代码
  1. float score = qWeight * vals.floatVal(doc);  

       那这里ValueSource又起什么作用呢,为什么不直接让FunctionQuery来构建FunctionValues,而是要引入一个中间角色ValueSource呢?

      因为FunctionQuery应该线程安全的,即允许多次查询共用同一个FunctionQuery实例,如果让FunctionValues直接依赖FunctionQuery,那可能会导致某个线程通过FunctionValues得到的docValuesField域值被另一个线程修改了,所以引入了一个ValuesSource,让每个FunctionQuery对应一个ValueSource,再让ValueSource去生成FunctionValues,因为docValuesField域值的正确性会影响到最后的评分。另外出于缓存原因,因为每次通过FunctionValues去加载docValuesField的域值,其实还是通过IndexReader去读取的,这就意味着有磁盘IO行为,磁盘IO次数可是程序性能杀手哦,所以设计CachingDoubleValueSource来包装ValueSource.不过CachingDoubleValueSource貌似还处在捐献模块,不知道下个版本是否会考虑为ValueSource添加Cache功能。

   

    ValueSource构造很简单,

Java代码  收藏代码
  1. public DoubleFieldSource(String field) {  
  2.     super(field);  
  3.   }  

    你只需要提供一个域的名称即可,不过要注意,这里的域必须是DocValuesField,不能是普通的StringField,TextField,IntField,FloatField,LongField。

    那FunctionQuery可以用来解决什么问题?举个例子:比如你索引了N件商品,你希望通过某个关键字搜索时,出来的结果优先按最近上架的商品显示,再按商品和搜索关键字匹配度高低降序显示,即你希望最近上架的优先靠前显示,评分高的靠前显示。

     下面是一个FunctionQuery使用示例,模拟类似这样的场景:

     书籍的出版日期越久远,其权重因子会按天数一天天衰减,从而实现让新书自动靠前显示

Java代码  收藏代码
  1. package com.yida.framework.lucene5.function;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.Map;  
  5.   
  6. import org.apache.lucene.index.DocValues;  
  7. import org.apache.lucene.index.LeafReaderContext;  
  8. import org.apache.lucene.index.NumericDocValues;  
  9. import org.apache.lucene.queries.function.FunctionValues;  
  10. import org.apache.lucene.queries.function.valuesource.FieldCacheSource;  
  11.   
  12. import com.yida.framework.lucene5.util.score.ScoreUtils;  
  13.   
  14. /** 
  15.  * 自定义ValueSource[计算日期递减时的权重因子,日期越近权重值越高] 
  16.  * @author Lanxiaowei 
  17.  * 
  18.  */  
  19. public class DateDampingValueSouce extends FieldCacheSource {  
  20.     //当前时间  
  21.     private static long now;  
  22.     public DateDampingValueSouce(String field) {  
  23.         super(field);  
  24.         //初始化当前时间  
  25.         now = System.currentTimeMillis();  
  26.     }  
  27.     /** 
  28.      * 这里Map里存的是IndexSeacher,context.get("searcher");获取 
  29.      */  
  30.     @Override  
  31.     public FunctionValues getValues(Map context, LeafReaderContext leafReaderContext)  
  32.             throws IOException {  
  33.         final NumericDocValues numericDocValues = DocValues.getNumeric(leafReaderContext.reader(), field);    
  34.         return new FunctionValues() {  
  35.             @Override  
  36.             public float floatVal(int doc) {  
  37.                 return ScoreUtils.getNewsScoreFactor(now, numericDocValues,doc);  
  38.             }  
  39.             @Override  
  40.             public int intVal(int doc) {  
  41.                 return (int) ScoreUtils.getNewsScoreFactor(now, numericDocValues,doc);  
  42.             }  
  43.             @Override  
  44.             public String toString(int doc) {  
  45.                 return description() + '=' + intVal(doc);  
  46.             }  
  47.         };  
  48.     }  
  49.       
  50. }  

 

Java代码  收藏代码
  1. package com.yida.framework.lucene5.util.score;  
  2.   
  3. import org.apache.lucene.index.NumericDocValues;  
  4.   
  5. import com.yida.framework.lucene5.util.Constans;  
  6.   
  7. /** 
  8.  * 计算衰减因子[按天为单位] 
  9.  * @author Lanxiaowei 
  10.  * 
  11.  */  
  12. public class ScoreUtils {  
  13.     /**存储衰减因子-按天为单位*/  
  14.     private static float[] daysDampingFactor = new float[120];  
  15.     /**降级阀值*/  
  16.     private static float demoteboost = 0.9f;  
  17.     static {  
  18.         daysDampingFactor[0] = 1;  
  19.         //第一周时权重降级处理  
  20.         for (int i = 1; i < 7; i++) {  
  21.             daysDampingFactor[i] = daysDampingFactor[i - 1] * demoteboost;  
  22.         }  
  23.         //第二周  
  24.         for (int i = 7; i < 31; i++) {             
  25.             daysDampingFactor[i] = daysDampingFactor[i / 7 * 7 - 1]  
  26.                     * demoteboost;  
  27.         }  
  28.         //第三周以后  
  29.         for (int i = 31; i < daysDampingFactor.length; i++) {  
  30.             daysDampingFactor[i] = daysDampingFactor[i / 31 * 31 - 1]  
  31.                     * demoteboost;  
  32.         }  
  33.     }  
  34.       
  35.     //根据相差天数获取当前的权重衰减因子  
  36.     private static float dayDamping(int delta) {  
  37.         float factor = delta < daysDampingFactor.length ? daysDampingFactor[delta]  
  38.                 : daysDampingFactor[daysDampingFactor.length - 1];  
  39.         System.out.println("delta:" + delta + "-->" + "factor:" + factor);  
  40.         return factor;  
  41.     }  
  42.       
  43.     public static float getNewsScoreFactor(long now, NumericDocValues numericDocValues, int docId) {  
  44.         long time = numericDocValues.get(docId);  
  45.         float factor = 1;  
  46.         int day = (int) (time / Constans.DAY_MILLIS);  
  47.         int nowDay = (int) (now / Constans.DAY_MILLIS);  
  48.         System.out.println(day + ":" + nowDay + ":" + (nowDay - day));  
  49.         // 如果提供的日期比当前日期小,则计算相差天数,传入dayDamping计算日期衰减因子  
  50.         if (day < nowDay) {  
  51.             factor = dayDamping(nowDay - day);  
  52.         } else if (day > nowDay) {  
  53.             //如果提供的日期比当前日期还大即提供的是未来的日期  
  54.             factor = Float.MIN_VALUE;  
  55.         } else if (now - time <= Constans.HALF_HOUR_MILLIS && now >= time) {  
  56.             //如果两者是同一天且提供的日期是过去半小时之内的,则权重因子乘以2  
  57.             factor = 2;  
  58.         }  
  59.         return factor;  
  60.     }  
  61.       
  62.     public static float getNewsScoreFactor(long now, long time) {  
  63.         float factor = 1;  
  64.         int day = (int) (time / Constans.DAY_MILLIS);  
  65.         int nowDay = (int) (now / Constans.DAY_MILLIS);  
  66.         // 如果提供的日期比当前日期小,则计算相差天数,传入dayDamping计算日期衰减因子  
  67.         if (day < nowDay) {  
  68.             factor = dayDamping(nowDay - day);  
  69.         } else if (day > nowDay) {  
  70.             //如果提供的日期比当前日期还大即提供的是未来的日期  
  71.             factor = Float.MIN_VALUE;  
  72.         } else if (now - time <= Constans.HALF_HOUR_MILLIS && now >= time) {  
  73.             //如果两者是同一天且提供的日期是过去半小时之内的,则权重因子乘以2  
  74.             factor = 2;  
  75.         }  
  76.         return factor;  
  77.     }  
  78.     public static float getNewsScoreFactor(long time) {  
  79.         long now = System.currentTimeMillis();  
  80.         return getNewsScoreFactor(now, time);  
  81.     }  
  82. }  

     

Java代码  收藏代码
  1. package com.yida.framework.lucene5.function;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5. import java.text.DateFormat;  
  6. import java.text.ParseException;  
  7. import java.text.SimpleDateFormat;  
  8. import java.util.Date;  
  9.   
  10. import org.apache.lucene.analysis.Analyzer;  
  11. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  12. import org.apache.lucene.document.Document;  
  13. import org.apache.lucene.document.Field;  
  14. import org.apache.lucene.document.Field.Store;  
  15. import org.apache.lucene.document.LongField;  
  16. import org.apache.lucene.document.NumericDocValuesField;  
  17. import org.apache.lucene.document.TextField;  
  18. import org.apache.lucene.index.DirectoryReader;  
  19. import org.apache.lucene.index.IndexReader;  
  20. import org.apache.lucene.index.IndexWriter;  
  21. import org.apache.lucene.index.IndexWriterConfig;  
  22. import org.apache.lucene.index.IndexWriterConfig.OpenMode;  
  23. import org.apache.lucene.index.Term;  
  24. import org.apache.lucene.queries.CustomScoreQuery;  
  25. import org.apache.lucene.queries.function.FunctionQuery;  
  26. import org.apache.lucene.search.IndexSearcher;  
  27. import org.apache.lucene.search.ScoreDoc;  
  28. import org.apache.lucene.search.Sort;  
  29. import org.apache.lucene.search.SortField;  
  30. import org.apache.lucene.search.TermQuery;  
  31. import org.apache.lucene.search.TopDocs;  
  32. import org.apache.lucene.store.Directory;  
  33. import org.apache.lucene.store.FSDirectory;  
  34. /** 
  35.  * FunctionQuery测试 
  36.  * @author Lanxiaowei 
  37.  * 
  38.  */  
  39. public class FunctionQueryTest {  
  40.     private static final DateFormat formate = new SimpleDateFormat("yyyy-MM-dd");  
  41.     public static void main(String[] args) throws Exception {  
  42.         String indexDir = "C:/lucenedir-functionquery";  
  43.         Directory directory = FSDirectory.open(Paths.get(indexDir));  
  44.           
  45.         //System.out.println(0.001953125f * 100000000 * 0.001953125f / 100000000);  
  46.         //创建测试索引[注意:只用创建一次,第二次运行前请注释掉这行代码]  
  47.         //createIndex(directory);  
  48.           
  49.           
  50.         IndexReader reader = DirectoryReader.open(directory);  
  51.         IndexSearcher searcher = new IndexSearcher(reader);  
  52.         //创建一个普通的TermQuery  
  53.         TermQuery termQuery = new TermQuery(new Term("title""solr"));  
  54.         //根据可以计算日期衰减因子的自定义ValueSource来创建FunctionQuery  
  55.         FunctionQuery functionQuery = new FunctionQuery(new DateDampingValueSouce("publishDate"));   
  56.         //自定义评分查询[CustomScoreQuery将普通Query和FunctionQuery组合在一起,至于两者的Query评分按什么算法计算得到最后得分,由用户自己去重写来干预评分]  
  57.         //默认实现是把普通查询评分和FunctionQuery高级查询评分相乘求积得到最终得分,你可以自己重写默认的实现  
  58.         CustomScoreQuery customScoreQuery = new CustomScoreQuery(termQuery, functionQuery);  
  59.         //创建排序器[按评分降序排序]  
  60.         Sort sort = new Sort(new SortField[] {SortField.FIELD_SCORE});  
  61.         TopDocs topDocs = searcher.search(customScoreQuery, null, Integer.MAX_VALUE, sort,true,false);  
  62.         ScoreDoc[] docs = topDocs.scoreDocs;  
  63.           
  64.         for (ScoreDoc scoreDoc : docs) {  
  65.             int docID = scoreDoc.doc;  
  66.             Document document = searcher.doc(docID);  
  67.             String title = document.get("title");  
  68.             String publishDateString = document.get("publishDate");  
  69.             System.out.println(publishDateString);  
  70.             long publishMills = Long.valueOf(publishDateString);  
  71.             Date date = new Date(publishMills);  
  72.             publishDateString = formate.format(date);  
  73.             float score = scoreDoc.score;  
  74.             System.out.println(docID + "  " + title + "                    " +   
  75.                 publishDateString + "            " + score);  
  76.         }  
  77.           
  78.         reader.close();  
  79.         directory.close();  
  80.     }  
  81.       
  82.     /** 
  83.      * 创建Document对象 
  84.      * @param title              书名 
  85.      * @param publishDateString  书籍出版日期 
  86.      * @return 
  87.      * @throws ParseException 
  88.      */  
  89.     public static Document createDocument(String title,String publishDateString) throws ParseException {  
  90.         Date publishDate = formate.parse(publishDateString);  
  91.         Document doc = new Document();  
  92.         doc.add(new TextField("title",title,Field.Store.YES));  
  93.         doc.add(new LongField("publishDate", publishDate.getTime(),Store.YES));  
  94.         doc.add(new NumericDocValuesField("publishDate", publishDate.getTime()));  
  95.         return doc;  
  96.     }  
  97.       
  98.     //创建测试索引  
  99.     public static void createIndex(Directory directory) throws ParseException, IOException {  
  100.         Analyzer analyzer = new StandardAnalyzer();  
  101.         IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);  
  102.         indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);  
  103.         IndexWriter writer = new IndexWriter(directory, indexWriterConfig);  
  104.           
  105.         //创建测试索引  
  106.         Document doc1 = createDocument("Lucene in action 2th edition""2010-05-05");  
  107.         Document doc2 = createDocument("Lucene Progamming""2008-07-11");  
  108.         Document doc3 = createDocument("Lucene User Guide""2014-11-24");  
  109.         Document doc4 = createDocument("Lucene5 Cookbook""2015-01-09");  
  110.         Document doc5 = createDocument("Apache Lucene API 5.0.0""2015-02-25");  
  111.         Document doc6 = createDocument("Apache Solr 4 Cookbook""2013-10-22");  
  112.         Document doc7 = createDocument("Administrating Solr""2015-01-20");  
  113.         Document doc8 = createDocument("Apache Solr Essentials""2013-08-16");  
  114.         Document doc9 = createDocument("Apache Solr High Performance""2014-06-28");  
  115.         Document doc10 = createDocument("Apache Solr API 5.0.0""2015-03-02");  
  116.           
  117.         writer.addDocument(doc1);  
  118.         writer.addDocument(doc2);  
  119.         writer.addDocument(doc3);  
  120.         writer.addDocument(doc4);  
  121.         writer.addDocument(doc5);  
  122.         writer.addDocument(doc6);  
  123.         writer.addDocument(doc7);  
  124.         writer.addDocument(doc8);  
  125.         writer.addDocument(doc9);  
  126.         writer.addDocument(doc10);  
  127.         writer.close();  
  128.     }  
  129. }  

   

 

    运行测试结果如图:

 

       demo代码请在最底下的附件里下载如果你需要的话,OK,打完收工!

 

      如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: