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); 这个方法也有一定缺陷,即不一定准确,而且会拖慢搜索进度。