5.1 Filed的Cache
有时,存在这样的需求:快速的访问每个Document的Field,但是Lucene只是做了反向索引,因此这种正向索引非常耗时。
Stored fields是一种解决方法,但是也很慢,特别当量大的时候,占用很多内存。
Field的Cache能很好地解决这个问题。
要求
Document必须只有一个Token!
用法
float[] weights = FieldCache.DEFAULT.getFloats(reader, “weight”);
之后,使用weights[docID]即可访问对应的Field值。
在第一次使用FieldCache后,Doc的数值会被缓存在内存中,直到Reader关闭。
因此,它将消耗大量内存。
尽量不要将“顶层Reader”传入FiledCache,防止因为reopoen导致的双倍内存消耗。
5.2 对检索结果排序
默认情况下,Lucene将按照相关性倒序排序,即最相关的在前面。
除了在前段进行排序外,本节讲述了一种Lucene提供的排序方法,注意:它将使用FieldCache,因此有可能导致大量内存消耗!
需要排序的Field字段,必须使用Field.Index.NOT_ANALYZED!!!
在IndexSearcher中,提供了下属函数
search(Query, Filter, int, Sort)
Sort即使排序器。
除此之外,还必须额外开启它,
IndexSearcher.IndexsetDefaultFieldSortScoring(doTrackScores,doMaxScore),要排序至少前面这个doTrackScores为true。
根据多字段排序
new SortField("category", SortField.STRING), SortField.FIELD_SCORE, new SortField("pubmonth", SortField.INT,true) )
如上所示:使用SortField可以组合多个关键字来排序。最后一个参数可省略,即是否reverse(逆向)。
一般来说,不用自己手写Sorter。
更改String的排序规则
默认String是采用compareTo比较的,也可以自己定义,即通过拓展Locale的方法来实现:
SortField (String field, Locale locale)
5.5 SpanTermQuery(精确控制距离的查询)
SpanTermQuery在查询的将基础上,提供了更为精确的距离控制,例如查询"President Obama"和"health care reform"距离相近的结果。如果传统的AND方法,可能查询出结果,但也许距离非常的远。
SpanTermQuery精确的控制需要全局的遍历Document中的索引。
SpanTermQuery共有6个子类,如下所示。
SpanFirstQuery:只匹配在Field开头出现的。
SpanNearQuery:匹配距离相近的。
SpanNotQuery:匹配不重叠的。
FieldMaskingSpanQuery:多Field查询
SpanOrQuery:SpanQuery的合集。
这些子类都继承自SpanTermQuery,这个类除了TermQuery的功能外,还记录了Position的信息。
使用这些子类的方法是
1.创建SpanTermQuery,
2.作为参数传入SpanFirstQuery的构造函数中
SpanFirstQuery
例如,需要查询"the quick brown fox jump over a lazy dog"开头的brown
SpanTermQuery brown = new SpanTermQuery("f","brown");
SpanFirstQuery sfq = new SpanFirstQuery(brown, 2);
2代表从开头起2个单词内。
例如这个例子中,2是不能查询到的,3才行。
SpansNearQuery
SpanQuery[] quick_brown_dog =
new SpanQuery[]{quick, brown, dog};//quick brown 和dog都是SpanTermQuery
SpanNearQuery snq =
new SpanNearQuery(quick_brown_dog, 0, true); //要求quick brown dog是连续的,显然查询不到
snq = new SpanNearQuery(quick_brown_dog, 5, true); //5,刚刚好因为brown和dog间差了5个!
再看一下第三个参数boolean的作用,表示是否按照数组中定义的顺序(order)来
例如下面的例子,仍然对于"the quick brown fox jump over a lazy dog"
snq = new SpanNearQuery(new SpanQuery[]{lazy, fox}, 3, false);
虽然lazy和fox之间距离是负的(顺序反了),但是如果指定order为false,则刚好可以查询到。
SpanNotQuery
SpanNotQuery将去除那些两个SpanQuery相互重叠的结果,更准确的说,是去除“中间重叠挖掉的那部分”。
例如有两个Document:"the quick brown fox jump over a lazy dog"和"the quick red fox jumps over the sleepy cat"
SpanNearQuery quick_fox =
new SpanNearQuery(new SpanQuery[]{quick, fox}, 1, true); //基础的SpanQuery,默认匹配2个Document
//匹配的这两个结果的中间重叠部分为:red/brown
SpanNotQuery quick_fox_dog = new SpanNotQuery(quick_fox, dog);//因为dog不是red或者brown,所以仍为两个
SpanNotQuery no_quick_red_fox = new SpanNotQuery(quick_fox, red); //现在只剩下brown那个doc了。
这种Query经常用于连接的时候。
SpanOrQuery
用途是:组合SpanQuery的结果,用法如下:
SpanOrQuery or = new SpanOrQuery(new SpanQuery[]{qf_near_ld, qf_near_sc});
qf_near_ld和qf_near_sc都是SpanNearQuery。
遗憾的是,目前lucene core尚不支持从QueryParser中解析并生成各种SpanQuery。
5.6 对搜索结果进行过滤(Filtering a search)
Filter用于对搜索结果进行过滤,可能是处于安全考虑(Doc的访问有权限),或者纯粹的再搜索需求。
TermRangeFilter
过滤Filed中在一定范围内的Term,用于String类型,字符请用NumericRangeFilter。
Filter filter = new TermRangeFilter("title2", "d", "j", true, true); //field是title2,在d*和j*之间,最后两个true表示包含大写和小写的Term。
Filter支持半开区间,例如:
filter = new TermRangeFilter("modified", null, jan31, false, true);//则左边没有下限。
或者用static方法构造:
filter = TermRangeFilter.Less("modified", jan31);
最后再search()的时候传入即可。
NumericRangeFilter
Filter filter = NumericRangeFilter.newIntRange("pubmonth",201001,201006,true, true); //过滤数字
如上所示,只支持static方法创建,需要按照Long,int等分别创建。
FieldCacheRangeFilter
FieldCacheRangeFilter与TermRangeFilter和NumericRangeFilter的功能几乎一样,但是使用了Field内置的Cache,因此有些时候具有更好的性能。
Filter filter = FieldCacheRangeFilter.newStringRange("title2", "d", "j", true, true);
filter = FieldCacheRangeFilter.newIntRange("pubmonth", 201001, 201006, true, true);
FieldCacheTermsFilter
基于Term的过滤器,根据Filed和Term进行过滤。
例如有一个Country的Field,用户可下拉选择结果Doc中部分含指定国家的Doc。
Filter filter = new FieldCacheTermsFilter("category", new String[] {"/health/alternative/chinese","/technology/computers/ai","/technology/computers/programming"});
Doc的名为category的Field中,含有任意上述一个都会被接受。在这种情况下,Doc的每个Field只能有1个Term!即必须是唯一确定的。
如果需要对多Term的Field进行过滤,可以使用TermsFilter,在contri中。
QueryWrapperFilter
QueryWrapperFilter可以将任意的Query包装成Filter,用于过滤。
TermQuery categoryQuery = new TermQuery(new Term("category", "/philosophy/eastern"));
Filter categoryFilter = new QueryWrapperFilter(categoryQuery);
SpanQueryFilter
与QueryWrapperFilter相似,SpanQueryFilter将SpanQuery包装起来,用作Filter。
panQuery categoryQuery = new SpanTermQuery(new Term("category", "/philosophy/eastern"));
Filter categoryFilter = new SpanQueryFilter(categoryQuery);
一般情况下,QueryWrapperFilter就足够了。
保证安全性的Filter
假设不同的Doc被不同Owner所有,要在前段根据用户进行过滤,则一种简单的策略是:
为每个Doc创建一个名为Owner的Filed,然后对它使用QueryWrapperFilter对最终的搜索结果按照Filter进行过滤(结合前端的信息)。
6.4节提供了一个更高级的方法解决这个问题。
使用BooleanQuery用作Filter
区别是,Boost的数值会不同,因为BooleanQuery会考虑所有的Doc的数值,而Filter不会。
PrefixFilter
可以对Term只为XX前缀的Doc进行过滤。
Filter prefixFilter = new PrefixFilter(new Term("category", "/technology/computers"));
使用Cache以获得更好的性能
所有的Filter都可以被包装以支持Cache的形式。
CachingWrapperFilter cachingFilter;
cachingFilter = new CachingWrapperFilter(filter);
对Filter进行Filter
FilteredDocIdSet是一个抽象类,通过重载其match方法来决定某个docID是否应该被过滤掉。
一般用于动态检查的时候使用,可自己拓展逻辑。
其他非Core的Filter
除了core的Filter之外,在contri中叶有其他的Filter,例如ChainedFilter:将Filter进行组合,允许复杂的链Filter。
6.4节介绍了如何自定义Filter。
5.7 自定义函数更高Score
org.apache.lucene.search.function包中定义了一系列函数,可用于对查询结果的相关性Score进行更改。
各种自定义函数
ValueSourceQuery是所有自定义函数的基类。
最基本的实现类是FieldScoreQuery,它从Field中静态地提取Score数值。
即给Doc添加如下Field,注意必须是not analyzerd not norms(保证是数值类型)
doc.add(new Field("score","42",Field.Store.NO,Field.Index.NOT_ANALYZED_NO_NORMS));
在查询的时候:
Query q = new FieldScoreQuery("score", FieldScoreQuery.Type.BYTE);
后面这个FieldScoreQuery.Type也可以选择INT、LONG等等,根据实际需要来。
CustomScoreQuery可以与其他Query相结合。
例如下面这个更改默认参数的例子:
Query q = new QueryParser(Version.LUCENE_30,"content",new StandardAnalyzer(Version.LUCENE_30)).parse("the green hat");
FieldScoreQuery qf = new FieldScoreQuery("score",FieldScoreQuery.Type.BYTE);
CustomScoreQuery customQ = new CustomScoreQuery(q, qf) {
public CustomScoreProvider getCustomScoreProvider(IndexReader r) {
return new CustomScoreProvider(r) {
public float customScore(int doc,float subQueryScore,float valSrcScore) {
return (float) (Math.sqrt(subQueryScore) * valSrcScore);
}
};
}
};
在这个构造函数customScore中,subQueryScore是Query默认的Score(CustomScoreQuery的q传入),valSrcScore是构造参数传入的FieldScoreQuery的静态Score(CustomScoreQuery的qf传入)。
定义函数来Boost最近更改的Doc
CustomScoreQuery可以 设定自己的BI逻辑,对Doc进行不同的Boost。
具体的例子见书吧。
5.8 同时搜索多个Index
在一个搜索引擎中可能存在多个Index,例如每个月生成一次Index,这样可以按照月份搜索。
可以使用MultiSearcher进行搜索。
IndexSearcher[] searchers = new IndexSearcher[2];
searchers[0] = new IndexSearcher(aTOmDirectory);
searchers[1] = new IndexSearcher(nTOzDirectory);
MultiSearcher searcher = new MultiSearcher(searchers);
TermRangeQuery query = new TermRangeQuery("animal","h","t",true, true);
TopDocs hits = searcher.search(query, 10);
ParallelMultiSearcher支持多线程同时搜索。
5.9 使用TermVetor搜索“相似书籍”
若创建Index的时候,指定保存TermVector,则可以使用TermVector来搜索类似结果,例如,搜索类似主题的:
TermFreqVector vector =
reader.getTermFreqVector(id, "subject");
BooleanQuery subjectQuery = new BooleanQuery();
for (String vecTerm : vector.getTerms()) {
TermQuery tq = new TermQuery(
new Term("subject", vecTerm));
subjectQuery.add(tq, BooleanClause.Occur.SHOULD);
}
5.10 使用FieldLoader读取Field
读取Filed需要耗费大量的资源,其实一个Doc中的众多Field不必在每次search的时候都全部读出!
我们可以使用FieldLoader来更改这个策略:
可以自己拓展FieldSelector,并覆盖下面这个抽象方法:
FieldSelectorResult accept(String fieldName);
它根据传入的fieldName来给出具体策略结果,FieldSelectorResult有下述几种情况:
LOAD:直接读取
LAZY_LOAD:直到Field.stringValue() 或者Field.binaryValue() 的时候再读取
NO_LOAD:不读取这个Field的
LOAD_AND_BREAK:读取这个Field并终止,不再读取其他Field
LOAD_FOR_MERGE:在合并segment的时候,使用内置的Field。
SIZE:只读取Field的大小,并添加一个4字节的SIZE的Field
SIZE_AND_BREAK:与SIZE类似,但只读取当前的SIZE,不再读取其他的。
Lucene的Core也提供了一些具体实现类:
LoadFirstFieldSelector:只读取第一个1个Field
MapFieldSelector:传入一个Map,Map指定了哪些Field你想读取,哪些你不想读取
SetBasedFieldSelector:传入两个Set,第一个Set是你想读取的,第二个是Lazy-Load读取的。
需要注意的是,FiledSelector本身也有开销,要在实际测试之后再谨慎使用!
5.11 杀死慢速搜索
可以使用TimeLimitingCollector来结束过慢的搜索,超时后抛出TimeExceededException异常。
TopScoreDocCollector topDocs = TopScoreDocCollector.create(10, false);
Collector collector = new TimeLimitingCollector(topDocs,1000);
searcher.search(q, collector);
这个方法也有一定缺陷,即不一定准确,而且会拖慢搜索进度。