Lucene5学习之CustomScoreQuery

简介:

 虽然前面我们已经集中学习过Query,但CustomScoreQuery当初略过了,今天就来学学这个Query.从类名上看,顾名思义,就大不略的猜得到它的干嘛用的。它是用来进行干预查询权重的,从而影响最终评分的,即评分公式中的queryNorm部分。

      一个索引文档的评分高低意味着它的价值大小,有价值的索引文档会优先返回并靠前显示,而影响评分的因素有Term在document中的出现频率,以及term在每个document中的出现频率,Term的权重等等,但这些因素都是固定的,并不会因为随着时间的改变而有所变化。比如你希望越是新出版的书籍权重应该越高,即出版日期距离当前时间越近权重越大。再比如你想实现我关注的用户发表的文章优先靠前显示,非关注用户发表的文章靠后显示等等,而CustomScoreQuery提供了这样一个接口来实现类似上述场景中的需求。你要做的就是

继承RecencyBoostCustomScoreQuery提供自己的CustomScoreProvider实现并重写其customScore方法,编写自己的实现逻辑。

      下面是使用示例:

Java代码   收藏代码
  1. package com.yida.framework.lucene5.function;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.lucene.index.LeafReaderContext;  
  6. import org.apache.lucene.index.NumericDocValues;  
  7. import org.apache.lucene.index.SortedDocValues;  
  8. import org.apache.lucene.queries.CustomScoreProvider;  
  9.   
  10. public class RecencyBoostCustomScoreProvider extends CustomScoreProvider {  
  11.     //权重倍数  
  12.     private double multiplier;  
  13.     // 从1970-01-01至今的总天数  
  14.     private int day;  
  15.     // 最大过期天数  
  16.     private int maxDaysAgo;  
  17.     // 日期域的名称  
  18.     private String dayField;  
  19.     // 域缓存值  
  20.     private NumericDocValues publishDay;  
  21.       
  22.     private SortedDocValues titleValues;  
  23.   
  24.     public RecencyBoostCustomScoreProvider(LeafReaderContext context,double multiplier,int day,int maxDaysAgo,String dayField) {  
  25.         super(context);  
  26.         this.multiplier = multiplier;  
  27.         this.day = day;  
  28.         this.maxDaysAgo = maxDaysAgo;  
  29.         this.dayField = dayField;  
  30.         try {  
  31.             publishDay = context.reader().getNumericDocValues(dayField);  
  32.             titleValues = context.reader().getSortedDocValues("title2");  
  33.         } catch (IOException e) {  
  34.             e.printStackTrace();  
  35.         }  
  36.     }  
  37.   
  38.     /** 
  39.      * subQueryScore:指的是普通Query查询的评分 
  40.      * valSrcScore:指的是FunctionQuery查询的评分 
  41.      */  
  42.     @Override  
  43.     public float customScore(int docId, float subQueryScore, float valSrcScore)  
  44.             throws IOException {  
  45.         String title = titleValues.get(docId).utf8ToString();  
  46.         int daysAgo = (int) (day - publishDay.get(docId));  
  47.         //System.out.println(title + ":" + daysAgo + ":" + maxDaysAgo);  
  48.         //如果在6年之内  
  49.         if (daysAgo < maxDaysAgo) {  
  50.             float boost = (float) (multiplier * (maxDaysAgo - daysAgo) / maxDaysAgo);  
  51.             return (float) (subQueryScore * (1.0 + boost));  
  52.         }  
  53.         return subQueryScore;  
  54.     }  
  55. }  

    

Java代码   收藏代码
  1. package com.yida.framework.lucene5.function;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.lucene.index.LeafReaderContext;  
  6. import org.apache.lucene.queries.CustomScoreProvider;  
  7. import org.apache.lucene.queries.CustomScoreQuery;  
  8. import org.apache.lucene.search.Query;  
  9.   
  10. public class RecencyBoostCustomScoreQuery extends CustomScoreQuery {  
  11.     // 倍数  
  12.     private double multiplier;  
  13.     // 从1970-01-01至今的总天数  
  14.     private int day;  
  15.     // 最大过期天数  
  16.     private int maxDaysAgo;  
  17.     // 日期域的名称  
  18.     private String dayField;  
  19.     public RecencyBoostCustomScoreQuery(Query subQuery,double multiplier,int day,int maxDaysAgo,String dayField) {  
  20.         super(subQuery);  
  21.         this.multiplier = multiplier;  
  22.         this.day = day;  
  23.         this.maxDaysAgo = maxDaysAgo;  
  24.         this.dayField = dayField;  
  25.     }  
  26.   
  27.     @Override  
  28.     protected CustomScoreProvider getCustomScoreProvider(  
  29.             LeafReaderContext context) throws IOException {  
  30.         return new RecencyBoostCustomScoreProvider(context,multiplier,day,maxDaysAgo,dayField);  
  31.     }  
  32. }  

    

Java代码   收藏代码
  1. package com.yida.framework.lucene5.function;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5. import java.util.Date;  
  6.   
  7. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  8. import org.apache.lucene.document.Document;  
  9. import org.apache.lucene.index.DirectoryReader;  
  10. import org.apache.lucene.index.IndexReader;  
  11. import org.apache.lucene.queryparser.classic.ParseException;  
  12. import org.apache.lucene.queryparser.classic.QueryParser;  
  13. import org.apache.lucene.search.IndexSearcher;  
  14. import org.apache.lucene.search.Query;  
  15. import org.apache.lucene.search.Sort;  
  16. import org.apache.lucene.search.SortField;  
  17. import org.apache.lucene.search.TopDocs;  
  18. import org.apache.lucene.store.Directory;  
  19. import org.apache.lucene.store.FSDirectory;  
  20.   
  21. import com.yida.framework.lucene5.util.Constans;  
  22. /** 
  23.  * CustomScoreQuery测试 
  24.  * @author Lanxiaowei 
  25.  * 
  26.  */  
  27. public class CustomScoreQueryTest {  
  28.       
  29.     public static void main(String[] args) throws IOException, ParseException {  
  30.         String indexDir = "C:/lucenedir";  
  31.         Directory directory = FSDirectory.open(Paths.get(indexDir));  
  32.         IndexReader reader = DirectoryReader.open(directory);  
  33.         IndexSearcher searcher = new IndexSearcher(reader);  
  34.           
  35.         int day = (int) (new Date().getTime() / Constans.PRE_DAY_MILLISECOND);  
  36.         QueryParser parser = new QueryParser("contents",new StandardAnalyzer());  
  37.         Query query = parser.parse("java in action");         
  38.         Query customScoreQuery = new RecencyBoostCustomScoreQuery(query,2.0,day, 6*365,"pubmonthAsDay");  
  39.         Sort sort = new Sort(new SortField[] {SortField.FIELD_SCORE,  
  40.             new SortField("title2", SortField.Type.STRING)});  
  41.         TopDocs hits = searcher.search(customScoreQuery, null, Integer.MAX_VALUE, sort,true,false);  
  42.   
  43.         for (int i = 0; i < hits.scoreDocs.length; i++) {  
  44.             //两种方式取Document都行,其实searcher.doc内部本质还是调用reader.document  
  45.           //Document doc = reader.document(hits.scoreDocs[i].doc);  
  46.             Document doc = searcher.doc(hits.scoreDocs[i].doc);  
  47.           System.out.println((1+i) + ": " +  
  48.                              doc.get("title") +  
  49.                              ": pubmonth=" +  
  50.                              doc.get("pubmonth") +  
  51.                              " score=" + hits.scoreDocs[i].score);  
  52.         }  
  53.         reader.close();  
  54.         directory.close();  
  55.     }  
  56. }  

    

Java代码   收藏代码
  1. package com.yida.framework.lucene5.sort;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.IOException;  
  6. import java.nio.file.Paths;  
  7. import java.text.ParseException;  
  8. import java.util.ArrayList;  
  9. import java.util.Date;  
  10. import java.util.List;  
  11. import java.util.Properties;  
  12.   
  13. import org.apache.lucene.analysis.Analyzer;  
  14. import org.apache.lucene.analysis.standard.StandardAnalyzer;  
  15. import org.apache.lucene.document.BinaryDocValuesField;  
  16. import org.apache.lucene.document.DateTools;  
  17. import org.apache.lucene.document.Document;  
  18. import org.apache.lucene.document.Field;  
  19. import org.apache.lucene.document.IntField;  
  20. import org.apache.lucene.document.NumericDocValuesField;  
  21. import org.apache.lucene.document.SortedDocValuesField;  
  22. import org.apache.lucene.document.SortedNumericDocValuesField;  
  23. import org.apache.lucene.document.StringField;  
  24. import org.apache.lucene.document.TextField;  
  25. import org.apache.lucene.index.IndexWriter;  
  26. import org.apache.lucene.index.IndexWriterConfig;  
  27. import org.apache.lucene.index.IndexWriterConfig.OpenMode;  
  28. import org.apache.lucene.store.Directory;  
  29. import org.apache.lucene.store.FSDirectory;  
  30. import org.apache.lucene.util.BytesRef;  
  31. /** 
  32.  * 创建测试索引 
  33.  * @author Lanxiaowei 
  34.  * 
  35.  */  
  36. public class CreateTestIndex {  
  37.     public static void main(String[] args) throws IOException {  
  38.         String dataDir = "C:/data";  
  39.         String indexDir = "C:/lucenedir";  
  40.   
  41.         Directory dir = FSDirectory.open(Paths.get(indexDir));  
  42.         Analyzer analyzer = new StandardAnalyzer();  
  43.         IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);  
  44.         indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);  
  45.         IndexWriter writer = new IndexWriter(dir, indexWriterConfig);  
  46.   
  47.         List<File> results = new ArrayList<File>();  
  48.         findFiles(results, new File(dataDir));  
  49.         System.out.println(results.size() + " books to index");  
  50.   
  51.         for (File file : results) {  
  52.             Document doc = getDocument(dataDir, file);  
  53.             writer.addDocument(doc);  
  54.         }  
  55.         writer.close();  
  56.         dir.close();  
  57.   
  58.     }  
  59.   
  60.     /** 
  61.      * 查找指定目录下的所有properties文件 
  62.      *  
  63.      * @param result 
  64.      * @param dir 
  65.      */  
  66.     private static void findFiles(List<File> result, File dir) {  
  67.         for (File file : dir.listFiles()) {  
  68.             if (file.getName().endsWith(".properties")) {  
  69.                 result.add(file);  
  70.             } else if (file.isDirectory()) {  
  71.                 findFiles(result, file);  
  72.             }  
  73.         }  
  74.     }  
  75.   
  76.     /** 
  77.      * 读取properties文件生成Document 
  78.      *  
  79.      * @param rootDir 
  80.      * @param file 
  81.      * @return 
  82.      * @throws IOException 
  83.      */  
  84.     public static Document getDocument(String rootDir, File file)  
  85.             throws IOException {  
  86.         Properties props = new Properties();  
  87.         props.load(new FileInputStream(file));  
  88.   
  89.         Document doc = new Document();  
  90.   
  91.         String category = file.getParent().substring(rootDir.length());  
  92.         category = category.replace(File.separatorChar, '/');  
  93.   
  94.         String isbn = props.getProperty("isbn");  
  95.         String title = props.getProperty("title");  
  96.         String author = props.getProperty("author");  
  97.         String url = props.getProperty("url");  
  98.         String subject = props.getProperty("subject");  
  99.   
  100.         String pubmonth = props.getProperty("pubmonth");  
  101.   
  102.         System.out.println("title:" + title + "\n" + "author:" + author + "\n" + "subject:" + subject + "\n"  
  103.                 + "pubmonth:" + pubmonth + "\n" + "category:" + category + "\n---------");  
  104.   
  105.         doc.add(new StringField("isbn", isbn, Field.Store.YES));  
  106.         doc.add(new StringField("category", category, Field.Store.YES));  
  107.         doc.add(new SortedDocValuesField("category"new BytesRef(category)));  
  108.         doc.add(new TextField("title", title, Field.Store.YES));  
  109.         doc.add(new Field("title2", title.toLowerCase(), Field.Store.YES,  
  110.                 Field.Index.NOT_ANALYZED_NO_NORMS,  
  111.                 Field.TermVector.WITH_POSITIONS_OFFSETS));  
  112.         //doc.add(new BinaryDocValuesField("title2", new BytesRef(title.getBytes())));  
  113.           
  114.         doc.add(new SortedDocValuesField("title2"new BytesRef(title.getBytes())));  
  115.         String[] authors = author.split(",");  
  116.         for (String a : authors) {  
  117.             doc.add(new Field("author", a, Field.Store.YES,  
  118.                     Field.Index.NOT_ANALYZED,  
  119.                     Field.TermVector.WITH_POSITIONS_OFFSETS));  
  120.         }  
  121.   
  122.         doc.add(new Field("url", url, Field.Store.YES,  
  123.                 Field.Index.NOT_ANALYZED_NO_NORMS));  
  124.         doc.add(new Field("subject", subject, Field.Store.YES,  
  125.                 Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));  
  126.   
  127.         doc.add(new IntField("pubmonth", Integer.parseInt(pubmonth),  
  128.                 Field.Store.YES));  
  129.         doc.add(new NumericDocValuesField("pubmonth", Integer.parseInt(pubmonth)));  
  130.         Date d = null;  
  131.         try {  
  132.             d = DateTools.stringToDate(pubmonth);  
  133.         } catch (ParseException pe) {  
  134.             throw new RuntimeException(pe);  
  135.         }  
  136.         int day = (int) (d.getTime() / (1000 * 3600 * 24));  
  137.         doc.add(new IntField("pubmonthAsDay",day, Field.Store.YES));  
  138.         doc.add(new NumericDocValuesField("pubmonthAsDay", day));  
  139.         for (String text : new String[] { title, subject, author, category }) {  
  140.             doc.add(new Field("contents", text, Field.Store.NO,  
  141.                     Field.Index.ANALYZED,  
  142.                     Field.TermVector.WITH_POSITIONS_OFFSETS));  
  143.         }  
  144.         return doc;  
  145.     }  
  146.   
  147. }  

    重点在创建索引域那里,由于我们需要在CustomScoreQuery里获取指定域的所有值,随后根据文档ID去获取特定域的值,这里Lucene使用了FieldCache即域缓存,如果不用域缓存,我们需要根据docId通过IndexReader对象去索引目录读取每个段文件从而获取某个域的值,一个文档意味着一次磁盘IO,如果你索引文档数据量大的话,那后果将会很严重,你懂的,为了减少磁盘IO次数,Lucene引入了域缓存概念,其实内部就是用一个Map<String,Object> 来存储的,map的key就是域的名称,看源码:

     IndexReader.getNumericDocValues

Java代码   收藏代码
  1. @Override  
  2.  public final NumericDocValues getNumericDocValues(String field) throws IOException {  
  3.    ensureOpen();  
  4.    Map<String,Object> dvFields = docValuesLocal.get();  
  5.   
  6.    Object previous = dvFields.get(field);  
  7.    if (previous != null && previous instanceof NumericDocValues) {  
  8.      return (NumericDocValues) previous;  
  9.    } else {  
  10.      FieldInfo fi = getDVField(field, DocValuesType.NUMERIC);  
  11.      if (fi == null) {  
  12.        return null;  
  13.      }  
  14.      NumericDocValues dv = getDocValuesReader().getNumeric(fi);  
  15.      dvFields.put(field, dv);  
  16.      return dv;  
  17.    }  
  18.  }  

    还有一点需要注意的是域缓存只对DocValuesField有效,这也是为什么创建索引代码那里需要add SortedDocValuesField,因为我们还需要根据该域进行排序,所以使用了SortedDocValuesField,

字符串类型可以用BinaryDocValuesField,数字类型可以使用NumericDocValuesField.域缓存是Lucene内部的一个高级API,对于用户来说,它是透明的,你只需要知道,使用DocValuesField可以利用域缓存来提升查询性能,但缓存也意味着需要有更多的内存消耗,所以在使用之前请进行性能测试,至于到底使不使用域缓存根据测试结果做好权衡。当你需要在Query查询内部去获取每个索引的某个域的值的时候,你就应该考虑使用域缓存。对于给定的IndexReader和指定的域,在首次访问域缓存的时候,会加载所有索引的该域的values放入缓存中(其实就是内存),是根据indexReader和域名两者联合起来确定唯一性,换句话说,你应该在多次查询中维持同一个IndexReader对象,因为每一个IndexReader都会有一套域缓存,如果你每次都new一个新的IndexReader,你会在内存中N个域缓存,这无疑是在内存中埋了N颗定时乍弹,而且这些你也无法利用域缓存。

    

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

或者加裙
一起交流学习!

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

目录
相关文章
|
5月前
|
索引
lucene入门使用
lucene入门使用
35 2
|
存储 自然语言处理 算法
Lucene学习总结
Lucene学习总结
104 0
Lucene学习总结
|
关系型数据库 MySQL 数据库
为什么要使用 Lucene|学习笔记
快速学习为什么要使用 Lucene
152 0
为什么要使用 Lucene|学习笔记
|
分布式计算 算法 Hadoop
什么是 lucene|学习笔记
快速学习 什么是 lucene
什么是 lucene|学习笔记
|
索引
lucene学习笔记
lucene学习笔记
138 0
|
开发框架 Java Apache
Lucene|学习笔记
快速学习 Lucene
121 0
|
Java 索引 自然语言处理
|
分布式计算 自然语言处理 Hadoop
|
存储 自然语言处理 算法
下一篇
无影云桌面