本文共 5936 字,大约阅读时间需要 19 分钟。
介绍的Lucene的几种评分方式,大部分的时候都能满足我们的大多数业务场景,但有些场合下可能我们使用另外一种评分策略,会更加灵活一点,上次介绍的评分主要是围绕着DefaultSimilarity这个类来介绍的,其实这个类控制评分的方式更加倾向于底层控制,而散仙下文要介绍的CustomScoreQuery这个类,则更加倾向于应用层面的控制。 为什么,有时候我们需要借助这个类来完成评分呢? 可能有时候我们会遇到如下类似的需求: 在一份论坛的索引里面有帖子的标题和帖子发布的日期(为了简化程序,假设按年来记录的),这个时候有如下需求,要求我们检索标题时,不仅要检索出与关键词最相关的帖子,而且还得是年份距现在相距不远的帖子,进行提拔加权,综上所述,这里面有2个关键因素,第一内容相关, 第二,近期时间的日期拥有的更高的加权。可以看出那么这个文档的评分是要结合这两个因素来完成最后的总的评分。 到这里可能有些人就会有疑问,为什么不对检索完的内容,按时间排序降序排序呢,这里可能会出现一个问题,如果是硬性的按时间降序排序,可能会破坏评分机制,因为默认的排序是按照评分降序排的,如果按照时间排序可能就会破坏原有的顺序,所以这个时候就需要我们统一下方式,要么用评分的方式来解决问题,那么用排序的问题来解决,显然统一评分的方式会更加适合这个场景。 测试的数据如下: Java代码 复制代码 收藏代码 1.Document doc=new Document(); 2. doc.add(new StringField("id", "1", Store.YES)); 3. doc.add(new TextField("name", "中国是一个多民族国家", Store.YES)); 4. doc.add(new IntField("date", 2012, Store.NO)); 5. writer.addDocument(doc); 6. 7. doc=new Document(); 8. 9. doc.add(new StringField("id", "2", Store.YES)); 10. doc.add(new TextField("name", "伟大的人啊", Store.YES)); 11. doc.add(new IntField("date", 2013, Store.NO)); 12. writer.addDocument(doc); 13. 14. doc=new Document(); 15. 16. doc.add(new StringField("id", "3", Store.YES)); 17. doc.add(new TextField("name", "伟大的祖国", Store.YES)); 18. doc.add(new IntField("date", 2010, Store.NO)); 19. writer.addDocument(doc); 没采用自定义评分的时候检索结果: Java代码 复制代码 收藏代码 1.2 伟大的人啊 0.5 2.3 伟大的祖国 0.5 我们可以采取两种方式,来完成这个方式,下面看第一种方式:基于CustomScoreProvider的方式, 我们统一对2010年的帖子加权为2,默认是与原来的评分是相乘的关系,代码如下: Java代码 复制代码 收藏代码 1.package com.qin.lucene20140123; 2. 3.import java.io.IOException; 4. 5.import org.apache.lucene.index.AtomicReaderContext; 6.import org.apache.lucene.queries.CustomScoreProvider; 7.import org.apache.lucene.search.FieldCache; 8.import org.apache.lucene.search.FieldCache.Ints; 9.import org.apache.lucene.search.similarities.DefaultSimilarity; 10. 11. 12./** 13. * @author 秦东亮 14. * Lucene技术交流群:324714439 15. * 实现评分提供的方式 16. * **/ 17.public class MyScoreProvider extends CustomScoreProvider { 18. AtomicReaderContext reader=null; 19. public MyScoreProvider(AtomicReaderContext context) { 20. super(context); 21. reader=context; 22. 23. // TODO Auto-generated constructor stub 24. } 25. 26. 27. @Override 28. public float customScore(int doc, float subQueryScore, float valSrcScore) 29. throws IOException { 30. 31. //FieldCache.DEFAULT.getTerms(reader.reader(), "date"); 32. //从域缓存里面加载索引字段的信息 33. Ints ints=FieldCache.DEFAULT.getInts(reader.reader(), "date", false); 34. 35. int date=ints.get(doc); 36. 37. float ss=1;//判断加权 38. if(date==2010){ 39. ss=2; 40. } 41. 42. /* 43. * 通过得分相乘放大分数 44. * 此处可以控制与原有得分结合的方式,加减乘除都可以 45. * **/ 46. return subQueryScore*valSrcScore*ss; 47. } 48. 49. 50. 51. 52.} 然后我们继承CustomScoreQuery,引用上文,我们定义的评分提供者,代码如下: Java代码 复制代码 收藏代码 1.package com.qin.lucene20140123; 2. 3.import java.io.IOException; 4. 5.import org.apache.lucene.index.AtomicReaderContext; 6.import org.apache.lucene.queries.CustomScoreProvider; 7.import org.apache.lucene.queries.CustomScoreQuery; 8.import org.apache.lucene.search.Query; 9./** 10. * 重写CustomScoreQuery 11. * 的CustomScoreProvider方法 12. * 引用我们自己的Provider 13. * 14. * **/ 15.public class MyQuery extends CustomScoreQuery { 16. 17. public MyQuery(Query subQuery) { 18. super(subQuery); 19. 20. // TODO Auto-generated constructor stub 21. } 22. 23. 24. 25. 26. 27. @Override 28. protected CustomScoreProvider getCustomScoreProvider( 29. AtomicReaderContext context) throws IOException { 30. 31. 32. /** 33. * 自定义的评分provider 34. * 35. * **/ 36. return new MyScoreProvider(context); 37. } 38. 39. 40. 41. 42. 43. 44. 45.} 最后,在检索的时候,使用我们自定义的的评分query,代码如下: Java代码 复制代码 收藏代码 1.QueryParser p=new QueryParser(Version.LUCENE_44, "name", new IKAnalyzer(true)); 2. Query query=p.parse(temp); 3.MyQuery myq=new MyQuery(query); 4. TopDocs top=searcher.search(myq, 10); 此时的检索结果和我们预期的一样: Java代码 复制代码 收藏代码 1.3 伟大的祖国 1.0 2.2 伟大的人啊 0.5 下面散仙,介绍第二种方式基于FunctionQuery的方式,这种方式需要我们自己重写ValueSource,来完成,代码如下: Java代码 复制代码 收藏代码 1.package com.qin.lucene20140123; 2. 3.import java.io.IOException; 4.import java.util.Map; 5. 6.import org.apache.lucene.index.AtomicReaderContext; 7.import org.apache.lucene.queries.function.FunctionValues; 8.import org.apache.lucene.queries.function.ValueSource; 9.import org.apache.lucene.queries.function.docvalues.FloatDocValues; 10.import org.apache.lucene.search.FieldCache; 11.import org.apache.lucene.search.FieldCache.Ints; 12. 13./** 14. * 15. * @author 秦东亮 16. * 17. * 重写ValueSource 18. * 返回外部的加权方式 19. * 20. * 21. * **/ 22.public class ScoreFunction extends ValueSource { 23. 24. 25. 26. 27. 28. @Override 29. public FunctionValues getValues(Map arg0, final AtomicReaderContext arg1) 30. throws IOException { 31. 32. 33. 34. return new FloatDocValues(this) { 35. 36. 37. @Override 38. public float floatVal(int doc) { 39. float s=1; 40. try { 41. /** 42. * 从域缓存里面 43. * 读取所需数据 44. * 45. * */ 46. Ints ints=FieldCache.DEFAULT.getInts(arg1.reader(),"date", false); 47. 48. 49. int a=ints.get(doc); 50. /** 51. * 对2010加权 52. * 53. * */ 54. if(a==2010){ 55. s=2; 56. } 57. } catch (IOException e) { 58. // TODO Auto-generated catch block 59. e.printStackTrace(); 60. } 61. 62. return s; 63. } 64. }; 65. } 66. 67. @Override 68. public int hashCode() { 69. // TODO Auto-generated method stub 70. return 0; 71. } 72. 73. @Override 74. public String description() { 75. // TODO Auto-generated method stub 76. return null; 77. } 78. 79. @Override 80. public boolean equals(Object arg0) { 81. // TODO Auto-generated method stub 82. return false; 83. } 84. 85. 86. 87. 88.} 然后,在检索时,就可以构造我们自己的自定义评分了, 核心代码如下: Java代码 复制代码 收藏代码 1.QueryParser p=new QueryParser(Version.LUCENE_44, "name", new IKAnalyzer(true)); 2. Query query=p.parse(temp); 3. /* 4. * 5. * 引用自己的 6. * 评分query 7. * **/ 8. CustomScoreQuery csq=new CustomScoreQuery(query,new FunctionQuery(new ScoreFunction())); 9. TopDocs top=searcher.search(csq, 10); 基于上次同样的检索条件,打印输出结果如下: Java代码 复制代码 收藏代码 1.3 伟大的祖国 0.49999997 2.2 伟大的人啊 0.24999999 除了,得分方式的不一样,我们发现对结果的排序都是一样的,由此,我们可以灵活选择我们所需要的方式,来完成我们的业务。转载地址:http://ywnoi.baihongyu.com/