《Lucene in Action》第五章—高级主题

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);

这个方法也有一定缺陷,即不一定准确,而且会拖慢搜索进度。


Leave a Reply

Your email address will not be published.