在Lucene4.4中,想要实现搜索结果按照时间倒序的效果:如果两个文档得分相同,那么就按照发布时间倒序排列;否则就按照分数排列。这种效果在Lucene4.6中实现起来极其简单,直接利用search接口的Sort参数即可达成,完全不需要像某些人说的重写Similarity那么麻烦。三两行代码的事情,体现了Make it simple, stupid的精髓。

首先来看看测试例子,这个例子中我建立了四个文档,按照内容-发布日期来表示分别是:

2004年光棍节攻略 , 20041111

2005年光棍节攻略 , 20051111

2006年光棍节攻略 , 20061111

游戏攻略 ,20141111

统一使用“光棍节攻略”来搜索它们,用户希望最新的光棍节攻略排在第一。

如果不做排序处理的话,用户体验非常糟糕:

1. package.hankcs.test;
2.  
3. import.apache.lucene.analysis.Analyzer;
4. import.apache.lucene.document.*;
5. import.apache.lucene.index.*;
6. import.apache.lucene.queries.CustomScoreQuery;
7. import.apache.lucene.queries.function.FunctionQuery;
8. import.apache.lucene.queryparser.classic.ParseException;
9. import.apache.lucene.queryparser.classic.QueryParser;
10. import.apache.lucene.search.*;
11. import.apache.lucene.store.Directory;
12. import.apache.lucene.store.LockObtainFailedException;
13. import.apache.lucene.store.RAMDirectory;
14. import.apache.lucene.util.Version;
15. import.wltea.analyzer.lucene.IKAnalyzer;
16.  
17. import.io.IOException;
18.  
19. /**
20.  * @author hankcs
21.  */
22. publicclassTestSortByTime
23. {
24. publicstaticvoid(String[])
25. {
26. // Lucene Document的主要域名
27. String="text";
28.  
29. // 实例化IKAnalyzer分词器
30. Analyzer=newIKAnalyzer();
31.  
32. Directory=null;
33. IndexWriter;
34. IndexReader=null;
35. IndexSearcher;
36. try
37. {
38. //索引过程**********************************
39. //建立内存索引对象
40. =newRAMDirectory();
41.  
42. //配置IndexWriterConfig
43. IndexWriterConfig=newIndexWriterConfig(Version.LUCENE_46,);
44. .setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
45. =newIndexWriter(directory,);
46. //写入索引
47. for(int=0;<3;++i)
48. {
49. int=2004+;
50. Document=newDocument();
51. .add(newTextField(fieldName,+"年光棍节攻略",Field.Store.YES));
52. .add(newIntField("date",*10000+1111,Field.Store.YES));
53. .addDocument(doc);
54. }
55. // 加入一个干扰文档
56. Document=newDocument();
57. .add(newTextField(fieldName,"游戏攻略",Field.Store.YES));
58. .add(newIntField("date",20141111,Field.Store.YES));
59. .addDocument(doc);
60. .close();
61.  
62. //搜索过程**********************************
63. //实例化搜索器
64. =DirectoryReader.open(directory);
65. =newIndexSearcher(ireader);
66.  
67. String="光棍节攻略";
68. //使用QueryParser查询分析器构造Query对象
69. QueryParser=newQueryParser(Version.LUCENE_46,,);
70. Query=.parse(keyword);
71. System.out.println("Query = "+);
72.  
73. //搜索相似度最高的5条记录
74. TopDocs=.search(query,5);
75. System.out.println("命中:"+.totalHits);
76. //输出结果
77. ScoreDoc[]=.scoreDocs;
78. for(int=0;<Math.min(5,.length);++)
79. {
80. Document=.doc(scoreDocs[i].doc);
81. System.out.print(targetDoc.getField(fieldName).stringValue());
82. System.out.print(" , "+.getField("date").numericValue());
83. System.out.println(" , "+[i].score);
84. }
85.  
86. }catch(CorruptIndexException)
87. {
88. .printStackTrace();
89. }catch(LockObtainFailedException)
90. {
91. .printStackTrace();
92. }catch(IOException)
93. {
94. .printStackTrace();
95. }catch(ParseException)
96. {
97. .printStackTrace();
98. }finally
99. {
100. if(ireader !=null)
101. {
102. try
103. {
104. .close();
105. }catch(IOException)
106. {
107. .printStackTrace();
108. }
109. }
110. if(directory !=null)
111. {
112. try
113. {
114. .close();
115. }catch(IOException)
116. {
117. .printStackTrace();
118. }
119. }
120. }
121. }
122. }

输出:

2004年光棍节攻略 , 20041111 , 0.71185887

2005年光棍节攻略 , 20051111 , 0.71185887

2006年光棍节攻略 , 20061111 , 0.71185887

游戏攻略 , 20141111 , 0.049675122

可以看到文档是严格按照分数排序的,如果分数相同,则按照索引顺序排序,导致最新的文章反而排在最下面。

使用search接口的Sort参数优化搜索结果:

1. package.hankcs.test;
2.  
3. import.apache.lucene.analysis.Analyzer;
4. import.apache.lucene.document.*;
5. import.apache.lucene.index.*;
6. import.apache.lucene.queries.CustomScoreQuery;
7. import.apache.lucene.queries.function.FunctionQuery;
8. import.apache.lucene.queryparser.classic.ParseException;
9. import.apache.lucene.queryparser.classic.QueryParser;
10. import.apache.lucene.search.*;
11. import.apache.lucene.store.Directory;
12. import.apache.lucene.store.LockObtainFailedException;
13. import.apache.lucene.store.RAMDirectory;
14. import.apache.lucene.util.Version;
15. import.wltea.analyzer.lucene.IKAnalyzer;
16.  
17. import.io.IOException;
18.  
19. /**
20.  * @author hankcs
21.  */
22. publicclassTestSortByTime
23. {
24. publicstaticvoid(String[])
25. {
26. // Lucene Document的主要域名
27. String="text";
28.  
29. // 实例化IKAnalyzer分词器
30. Analyzer=newIKAnalyzer();
31.  
32. Directory=null;
33. IndexWriter;
34. IndexReader=null;
35. IndexSearcher;
36. try
37. {
38. //索引过程**********************************
39. //建立内存索引对象
40. =newRAMDirectory();
41.  
42. //配置IndexWriterConfig
43. IndexWriterConfig=newIndexWriterConfig(Version.LUCENE_46,);
44. .setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
45. =newIndexWriter(directory,);
46. //写入索引
47. for(int=0;<3;++i)
48. {
49. int=2004+;
50. Document=newDocument();
51. .add(newTextField(fieldName,+"年光棍节攻略",Field.Store.YES));
52. .add(newIntField("date",*10000+1111,Field.Store.YES));
53. .addDocument(doc);
54. }
55. // 加入一个干扰文档
56. Document=newDocument();
57. .add(newTextField(fieldName,"游戏攻略",Field.Store.YES));
58. .add(newIntField("date",20141111,Field.Store.YES));
59. .addDocument(doc);
60. .close();
61.  
62. //搜索过程**********************************
63. //实例化搜索器
64. =DirectoryReader.open(directory);
65. =newIndexSearcher(ireader);
66.  
67. String="光棍节攻略";
68. //使用QueryParser查询分析器构造Query对象
69. QueryParser=newQueryParser(Version.LUCENE_46,,);
70. Query=.parse(keyword);
71. System.out.println("Query = "+);
72.  
73. //搜索相似度最高的5条记录
74. Sort=newSort(newSortField("text",SortField.Type.SCORE),newSortField("date",SortField.Type.INT,true));
75. TopDocs=.search(query,5,);
76. System.out.println("命中:"+.totalHits);
77. //输出结果
78. ScoreDoc[]=.scoreDocs;
79. for(int=0;<Math.min(5,.length);++)
80. {
81. Document=.doc(scoreDocs[i].doc);
82. System.out.print(targetDoc.getField(fieldName).stringValue());
83. System.out.print(" , "+.getField("date").numericValue());
84. System.out.println(" , "+[i].score);
85. }
86.  
87. }catch(CorruptIndexException)
88. {
89. .printStackTrace();
90. }catch(LockObtainFailedException)
91. {
92. .printStackTrace();
93. }catch(IOException)
94. {
95. .printStackTrace();
96. }catch(ParseException)
97. {
98. .printStackTrace();
99. }finally
100. {
101. if(ireader !=null)
102. {
103. try
104. {
105. .close();
106. }catch(IOException)
107. {
108. .printStackTrace();
109. }
110. }
111. if(directory !=null)
112. {
113. try
114. {
115. .close();
116. }catch(IOException)
117. {
118. .printStackTrace();
119. }
120. }
121. }
122. }
123. }

输出结果:

命中:4

2006年光棍节攻略 , 20061111 , NaN

2005年光棍节攻略 , 20051111 , NaN

2004年光棍节攻略 , 20041111 , NaN

游戏攻略 , 20141111 , NaN

我们看到“2006年光棍节攻略”因为时间比较新,并且相关性高,就排在了第一。“2005年光棍节攻略”相关度相同,因为时间旧就排在后面一点,而干扰文档“游戏攻略”即使时间最新,因为不相关的原因排在最后面。这种效果正好是我想要的,极大提升了用户体验。