9.搜索结果的处理和显示

1.       提取搜索结果

Lucene 中,搜索结果保存在 Hits 对象中,提取搜索结果就是从 Hits 对象中提取出文档,然后再从文档中提取各个字段。

1.1   Hits 对象的方法

l         Document doc(int n) 返回指定序号的 Document Doc 对象的 getField(“”) 方法获取字段

l         Int id(int n) 返回指定序号的 Document id 属性

l         Int length() 返回 Hits 对象的长度,即 Hits 对象中包含的 Document 的数量

l         Float score(int n) 返回指定序号的 Document score 属性

l         Iterator iterator() 返回一个 Iterator 对象用于导航 Hits 。将 Hits iterator 方法返回的 Iterator 对象转换成 HitIterator 对象后,就可以用 HitIterator 对象的 next 方法获得 Hit 对象,这个 hit 对象就是 Hits 的个体,具有 get(“ 字段名 ”) getDocument(), getScore() 等方法,可以获得某 Document 的一切信息。

1.2   性能说明

Hits 对象有内部缓存,搜索时,如果 Hits 内部已经缓存了一些记录,如果需要返回的记录在其缓存里, Lucene 将直接从缓存中提取记录,从而加快搜索。

Hits 对象的缓存机制,使得 Lucene 的查询性能很高,因此在做分页显示的时候,可以对用户的每次请求进行新的查询,而不是把 Hits 对象保留。(点第几页都重新搜索)

为加快速度,还可以把文件索引读入内存,建立起内存索引,然后执行搜索。方法如下:

RAMDirectory(Directory dir)

RamDirectrory(File dir)

RamDirectory(String dir);

如:

//search

RAMDirectory rd = new RAMDirectory(indexPath);

//IndexSearcher

IndexSearcher searcher = new IndexSearcher(rd);

2.       过滤搜索结果

过滤方式有两种:在搜索结果提取出来后过滤;把过滤条件加在搜索条件中。看起来后者更具优势,但目前,后者的实现是基于前者的,所以更耗内存,不如使用前者。

2.1   简单的过滤

2.2   利用 Filter 类过滤

抽象类,自定义的过滤器只需要覆盖它的一个方法:

BitSet bits(IndexReader reader);

过滤器在搜索结果显示之前就得到了 IndexReader 对象,从而将数据进行拦截来完成过滤。其子类有: QueryFilter RangeFilter PrefixFilter ChainedFilter CachingWrapperFilter 等。 QueryFilter 类的结果被缓存,其它类都不被缓存。

2.3   QueryFilter 类过滤

应用最广,结果被缓存

//IndexSearcher

              IndexSearcher searcher = new IndexSearcher(rd);

                           

              //Term & Query

              String searchField = "text";

              String searchPhrase = "love";

              Term t = new Term(searchField, searchPhrase);

              TermQuery q = new TermQuery(t);

             

              //Filter ,从上面的结果中过滤出包含“ genius ”的结果

              Term te = new Term(searchField,"genius");

              TermQuery tq = new TermQuery(te);

              QueryFilter filter = new QueryFilter(tq);

             

              //Hits

              Hits hs = searcher.search(q,filter);

使用 RangeQuery 构建 QueryFilter 过滤

//Filter id 号在 0-2 之间

              Term tb = new Term("id","0");

              Term te = new Term("id","2");            

              RangeQuery rq = new RangeQuery(tb,te,false);

              QueryFilter filter = new QueryFilter(rq);

使用 PrefixQuery 构建 QueryFilter

//Filter

              Term te = new Term("text","people");

              PrefixQuery pq = new PrefixQuery(te);             

              QueryFilter filter = new QueryFilter(pq);

2.4   PrefixFilter 前缀过滤

//Filter

              Term te = new Term("text","people");         

              PrefixFilter filter = new PrefixFilter(te);

2.5   RangeFilter

结果没有被缓存

用于从搜索结果中得到满足某一范围要求的记录,上面的 RangeQuery QueryFilter 的组合与单独使用 RangeFilter 的效果是一样的。

RangeFilter(String fieldname, String lowerTerm, String upperTerm, Boolean includedLower, Boolean includedUpper);

参数:字段名称,下边界,上边界,是否包含上边界,是否包含下边界

静态方法 Less 设定小于等于上边界的过滤器, More 设定大于等于下边界的过滤器。

Static RangeFilter Less(String filedName, String upperTerm);

Static RangeFilter More(String filedName, String upperTerm);

//Filter           

              RangeFilter filter = new RangeFilter("id","0","2",false,false);

使用 More 方法的例子:

//Filter

RangeFilter filter = RangeFilter.More("id","1");

2.6   ChainedFilter

用来将多个过滤器连接起来,从而达到多重过滤的效果,三个构造方法:

ChainedFilter(Filter[] chain)

ChainedFilter(Filter[] chain, int logic);

ChainedFilter(Filter[] chain, int[] logicArray);

参数:过滤器数组,逻辑关系: ChainedFilter.AND, ChainedFitler.OR 等常量来表示,默认为逻辑或。

BooleanQuery QueryFilter 结合也可以达到 ChainedFilter 的效果。

使用 ChainedFilter 需要把 contrib/misc 目录下的 lucene-misc-2.1.0.jar 放入编译路径

1 :简单的组合

//Filter

RangeFilter rf = RangeFilter.More("id","1"); 范围过滤器

 

Term te = new Term("text","people");

PrefixFilter pf = new PrefixFilter(te); 前缀过滤器

 

ChainedFilter filter = new ChainedFilter(new Filter[]{rf,pf}); 默认或逻辑

ChainedFilter filter = new ChainedFilter(new Filter[]{rf,pf}, ChainedFilter.AND);

例二:使用 BooleanQuery QueryFilter ,效果相当于 ChainedFilter

              //IndexSearcher

        IndexSearcher searcher = new IndexSearcher(rd);

                     

        //Term & Query

        String searchField = "text";

        String searchPhrase = "love";

        Term t = new Term(searchField, searchPhrase);

        TermQuery q = new TermQuery(t);

       

        //Filter

        Term te = new Term("text","you");

        TermQuery tq = new TermQuery(te);    // 普通词项搜索

       

        Term tr = new Term("text","xiaoyue");

        PrefixQuery pq = new PrefixQuery(tr);     // 前缀搜索

       

        BooleanQuery bq = new BooleanQuery();  // BooleanQuery            

        bq.add(tq,BooleanClause.Occur.MUST);

        bq.add(pq,BooleanClause.Occur.MUST);

       

        QueryFilter filter = new QueryFilter(bq);   //QueryFilter

       

        //Hits

        Hits hs = searcher.search(q,filter);

2.7   CachingWrapperFilter 的使用

由于除了 QueryFilter 之外的其它 Filter 自身都没有缓存,所以, CachingWrapperFilter 被开发出来用于包装其它 Filter ,从而使之具有缓存功能。构造方法如下:

CachingWrapperFilter(Filter filter);

只需在其它过滤器语句后面,加上这样一句即可:

CachingWrapperFilter(Filter filter);

CachingWrapperFilter filter = new CachingWrapperFilter(pf);

2.8   FilterQuery 的使用

FilteredQuery 是一种带有过滤器的特殊 Query 类,它是一种包装器,将原始的 Query 对象和某个过滤器结合起来。构造方法如下:

FilterQuery(Query query, Filter fitler);

因此,我们有了两种方式过滤搜索结果:

(1)              使用 Query Filter ,得到 Filter ,然后使用 search(Query 对象, Filter 对象 ) 的方法执行搜索,上面的示例大都如此;

(2)              使用 Query Filter ,得到 Query ,然后使用 search(Query 对象 ) 的方法执行搜索。( FilterQuery

//Term & Query    创建 Query

        //original query

        String searchField = "text";

        String searchPhrase = "love";

        Term t = new Term(searchField, searchPhrase);

        TermQuery qo = new TermQuery(t);

       

        //Filter  创建过滤器

        Term te = new Term("text","people");

        PrefixFilter filter = new PrefixFilter(te);

       

        //new query 结合 Query Filter

        FilteredQuery q = new FilteredQuery (qo,filter);

 

//Hits

        Hits hs = searcher.search(q);

 

2.9   综述

Lucece 为了过滤搜索结果,提供了许多过滤器类,如同那些 Query 类一样,许多类都是不必要的,只是为了编程的方便。

对于过滤器的一些建议:

l         过滤器消耗资源,慎用

l         如果需要过滤结果,最好是将搜索结果按某些自定义的条件显示出来,而不使用过滤器

l         如果一定要使用过滤器,应使用 QueryFilter ,因为它的结果被缓存了,再次执行过滤搜索时,速度更快

l         如果一定要使用过滤器,而且一定要使用 QueryFilter 之外的其它 Filter ,那么应使用 CachingWrapperFilter 为其封装(实现缓存)

 

评:貌似都可以用 Query 的那些类生成 Query ,然后再使用 QueryFilter 进行过滤。

 

3.       搜索结果排序

Lucene 默认是按文档得分进行排序的,我们可以按照其它规则进行排序。 Lucene 不进行链接结构分析。

3.1   按照文档得分排序

(1)      默认排序规则

//Hits

              Hits hs = searcher.search(q);       

              int num = hs.length();

 

              //view details

              for(int i=0;i<num;i++)

              {

                     //get document

                     doc = hs.doc(i);

                    

                     //fields

                     id = doc.getField("id").stringValue();

                                         

                     //score

                     float score = hs.score(i);   // 获取得分值

}

(2)      修改默认得分

最常用的修改得分的方法如下:

Void setBoost(float boost);

该方法修改 boost 因素,这是影响得分的主要因素之一。

Document Field 类都有这个方法, Document 类的 setBoost 方法用来设定其所有 Field boost 值; Field 类的 setBoost 方法用来设定该 Field boost 值。对 boost 的设定是在建立索引的过程中进行的,也即对某个文档调用 addDocument 之前。

例一:为 Document 设置 boost

IndexWriter writer = new IndexWriter(rd,new StandardAnalyzer());  

       

        Document doc = null;

        Field field = null;

        String id = null;

        String text = null;

       

        //doc 0

        doc = new Document();

        id = "0";

        field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);

        doc.add(field);

        text = "i love you, my mother land! ";

        field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED);

        doc.add(field);

       

        doc.setBoost(2.0f);

        writer.addDocument(doc);

例二:为 Field 设置 boost

Document doc = null;

              Field field = null;

              String id = null;

              String text = null;

             

              //doc 0

              doc = new Document();

              id = "0";

              field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);

              doc.add(field);

              text = "i love you, my mother land! ";

              field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED);             

              field.setBoost(2.0f); 为这一字段进行设置

              doc.add(field);

 

              writer.addDocument(doc);

最终的 boost 值未必和我们设置的一样,因为我们的设置只代表我们对文档排名的期待,最终值要通过进一步的计算才能转换成文档的真实 boost 值。

(3)      查看得分计算过程

Searcher 类提供了 explain 方法用来查看得分的计算过程。调用方法:

Explanation Explain(Query, int)

参数:被解释的 Query 对象,索引中文档序号,返回值是 Explanation 对象。该对象有 getDetails() isMatch() 等方法。

//IndexSearcher

        IndexSearcher searcher = new IndexSearcher(rd);

       

        //Term & Query

        String searchField = "text";

        String searchPhrase = "love";

        Term t = new Term(searchField, searchPhrase);

        TermQuery q = new TermQuery(t);

       

        //Hits

        Hits hs = searcher.search(q);

       

//view details

        int num = hs.length();          

        for(int i=0;i<num;i++)

        {

               //get document

               doc = hs.doc(i);

              

               //fields

               id = doc.getField("id").stringValue();

               text = doc.getField("text").stringValue();

              

               //document id

               int did = hs.id(i);

              

               //boost

               float boost = doc.getBoost();

              

               //score

               float score = hs.score(i);

              

               String explaination = searcher.explain(q,i).toString();

              

               sb.append("document " + did + ":" + doc + "/n");

               sb.append("boost:" + boost + "/n");

               sb.append("score:" + score + "/n");

               sb.append("id:" + id + "/n");

               sb.append("text:" + text + "/n");

               sb.append("explaination:/n" + explaination + "/n");

              

               sb.append("------------------- " + "/n/n");

        }

 

        searcher.close();

结果:

0.75 = (MATCH) fieldWeight(text:love in 0), product of:

1.0 = tf(termFreq(text:love)=1)     注释:在该文档中出现一次

1.0 = idf(docFreq=2)             注释:共两个文档,各出现一次

0.75 = fieldNorm(field=text, doc = 0)

要注意,使用 explain 方法是要付出代价的,这代价相当于执行对整个索引的查询。所以,除非要做改善得分算法一类的基础研究,否则不要使用该方法。

3.2   自定义排序规则

如按日期或多个字段组合排序,自定义排序规则需要使用 sort 类,构造方法如下:

(1)       sort() :系统自己的排序规则

(2)       sort(String field) :按照指定的某个 field 排序,参数是 field

(3)       sort(String[] fields) :按照指定的多个 field 进行排序

(4)       sort(SortField field) :按照指定的某个 field 排序,参数不是原始 field 名,而是 SortField 对象

(5)       sort(SortField[] fields) :按照指定的多个 field 排序

(6)       sort(String field, Boolean reverse) :按照指定的某个 field 排序, reverse 指明是降序还是升序,默认为 false 升序

两个静态属性:

1 Public static final Sort RELEVANCE

使用 RELEVANCE 属性时相当于没有使用 sort 方法,它将排序规则交给系统决定,默认就是,不需要专门设置

2 Public static final Sort INDEXORDER

使用 INDEXORDER 属性相当于按照索引内部的文档编号进行排序

在构造好 Sort 对象后,就可以用 search(Query 对象 , Sort 对象 ) 执行搜索了

注意:

l         被指定为排序依据的字段必须被索引且不能被分词(即使用 Field.Index.UN_TOKENIZED ),试图对分词了的字段作排序时,会抛出异常

l         被指定为排序依据的字段可以存储也可以不存储( Field.Store.YES Field.Store.NO

l         被指定为排序依据的字段的值类型必须是可排序的整型、浮点型或字符型。系统会根据第一个值自动判断字段类型。整型、浮点型效率高,字符型排序消耗资源多,意义不大。

3.3   让系统决定如何排序

              Hits hs = searcher.search(q,Sort.INDEXORDER);

3.4   按照索引中的文档编号排序

              Hits hs = searcher.search(q,Sort.INDEXORDER);

3.5   按照文本字段排序

//Hits sort(String field, Boolean reverse)

Sort sort = new Sort("time",true);

Hits hs = searcher.search(q,sort);

3.6   指定字段的数据类型

指定数据类型,需要用到 SortField 类。

l         SortField(String field)

l         SortField(String field, Boolean reverse)

l         SortField(String field, int type); type 是字段类型,值为六个静态属性

Ø           AUTO: 系统自己猜

Ø           CUSTOM :用户自己指定某个排序类型

Ø           FLOAT :指定排序字段为 float 类型

Ø           INT

Ø           STRING

Ø           SCORE :指定按照文档得分排序

l         SortField(String field, int type, Boolean reverse)

示例:

//Hits

              SortField sf = new SortField("id",SortField.INT,true); id 字段,整型,逆序

              Sort sort = new Sort(sf);

              Hits hs = searcher.search(q,sort);

3.7   按多个字段排序

只需将作为排序依据的字段合起来,以数组形式传递给 sort 方法即可。

String[] fields = new String[]{"id","age"};

Sort sort = new Sort(fields);

Hits hs = searcher.search(q,sort);

说明:按照{ ”id”, “age” }排序,以 id 字段为主, id 字段相同时,以 age 排序。

没有指明升降顺序,以默认升序排列

如果要让排序所依据的多个字段按照不同的升降顺序排列,就需要独立的 SortField 对象,在 SortField 对象中指定字段的排序规则,然后使用 sort(SortField[] fields) 构造方法。

//Hits

SortField sf1 = new SortField("id",false);

SortField sf2 = new SortField("age",true);

SortField[] fields = new SortField[]{sf1,sf2};

Sort sort = new Sort(fields);

 

Hits hs = searcher.search(q,sort);

3.8   综述

排序是很耗资源的,越复杂消耗越大,通常按照得分排序就足够了

另外, Lucene 提供了对于复杂自定义字段的排序接口,例如希望将三维坐标值以字符串形式保存在索引中,然后在提取出来时进行排序。

为了提高效率,我们可以在建立索引的时候,为某个不方便排序的字段建立一个辅助字段,将复杂数据类型转换成可以表示其大小的数值存储在辅助字段中,这样就容易排序了。

 

4.       高亮显示

关键词背景色、换颜色、加粗、斜体等,网页中的高亮是通过格式转换的方式完成的。

4.1   高亮显示的基本方式

手工实现高亮显示不大方便,有人开发了一套类库,主要类是 org.apache.lucene.search.highlight.Higelighter ,与其配合的还有 Fragmenter Scorer Formatter Encoder 等类。

高亮原理:

l         建立索引时,在文档相关字段(需要高亮处理的字段,被搜索的字段)中记录 Term 的位置

l         搜索时,利用字段中记录的词条的位置信息,将修饰符号添加进去,从而改变了搜索关键词的显示格式,达到突出显示的目的。

(1)       建立索引时,在文档相关字段中记录词条位置

Field field = new Field(“text”,text, Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.WITH_POSITIONS_OFFSETS );

这是个新的 Field 构造方法,可以将词条位置信息记录进去。

(2)       在搜索时修改词条显示格式

a. 首先要构造 Highlighter 对象

Highlighter(Scorer fragmentScore)

参数类型 Scorer Scorer 是一个接口,常用子类是 QueryScorer ,可以由 QueryScorer(Query query) 方法来构造。

Term t = new Term(“id”, 2);

Query q = new TermQuery(t);

QueryScorer qs = new QueryScorer(q);

Highlighter hl = new Highlighter(qs);

b. 再设置文本分块

Highlighter 类通过 setTextFragmenter(Fragmenter fragmenter) 方法设定文本的分块,参数 Framenter 是一个接口,其最常用的子类是 SimpleFragmenter ,可以通过 SimpleFragmenter() SimpleFragmenter(int fragmentSize) 方法构造。参数表示分块的大小,即显示给用户的含有关键词的文本块的大小。

SimpleFragmenter sf = new SimpleFragmenter(60);

highlighter.setTextFragmenter(sf);

c. 建立 TermPositionVector 对象

TermPositionVector tpv = (TermPositionVector)IndexReader 对象 .getTermFreqVector( 文档编号,被搜索字段 )

d. 设定高亮显示块的分隔符号

int maxNumFragmentsRequired = 3;

String fragmentSeparater = “…”

e. 最后,获得高亮处理后的结果

TokenStream tokenStream = TokenSouces.getTokenStream(tpv);

String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired, fragmentSeparator);

 

示例:

import org.apache.lucene.search.highlight.*;

创建索引时:

//doc 0

              doc = new Document();

              id = "0";

              field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);

              doc.add(field);

              text = "i love you, my mother land!i love you, my mother land!i love you, my mother land! ";

              field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS );

              doc.add(field);

              time = "2007-05-28";

              field = new Field("time", time ,Field.Store.YES, Field.Index.UN_TOKENIZED);

              doc.add(field);

              writer.addDocument(doc);

 

结果输出时

//IndexSearcher

        IndexSearcher searcher = new IndexSearcher(rd);

       

        //Term & Query

        String searchField = "text";

        String searchPhrase = "love";

        Term t = new Term(searchField, searchPhrase);

        TermQuery q = new TermQuery(t);

       

        //Hits

        Hits hs = searcher.search(q,Sort.RELEVANCE);

 

        //highlight

        Highlighter highlighter =new Highlighter(new QueryScorer(q));

        SimpleFragmenter sf = new SimpleFragmenter(10);

        highlighter.setTextFragmenter( sf );

       

        for (int i = 0; i < hs.length(); i++)

        {

               text = hs.doc(i).get("text");

              

               int maxNumFragmentsRequired = 3;                 

               String fragmentSeparator = "...";

              

               TermPositionVector tpv = (TermPositionVector)searcher.getIndexReader().getTermFreqVector(hs.id(i),"text");

              

               TokenStream tokenStream=TokenSources.getTokenStream(tpv);

              

               String result = highlighter.getBestFragments( tokenStream, text, maxNumFragmentsRequired, fragmentSeparator);

              

               System.out.println(result); 

        }

 

        searcher.close();

}

 

结果:

索引前文本:

  text = "i love you, my mother land!i love you, my mother land!i love you, my mother land! ";

输出结果:

  I <B>love</B> … <B>love</B> you… <B>love</B> you, my

4.2   高亮显示的基本设置

(1)       如果一个文档包含多个被搜索的关键词,则可以限定显示出来的关键词的数量,用 maxNumFragmentsRequired 进行设置

(2)       高亮处理后的结果是一段文本,可以在 SimpleFragmenter 的构造方法中指定显示给用户的文本长度 SimpleFragmenter sf = new SimpleFragmenter(10);

(3)       通常,我们设置含有关键词的文本块直接使用“ ”符号来连接,也可以通过 String fragmentSeparator = "..." 改为别的符号

(4)       默认情况下,高亮处理是使用“ <b></b> ”来修饰关键词的,可以自定义修饰格式

4.3   为高亮显示设置新的格式

默认情况下,高亮处理是使用“ <b></b> ”来修饰关键词的,可以用 SimpleHTMLFormatter 类进行修改,

Highlighter 的一种构造方法:

Hightlighter(Formatter formatter, Scorer fragmentScorer);

SimpleHTMLFormatter 也就是格式器,有两个构造方法:

1 SimpleHTMLFormatter()

默认格式“ <B></B>

2 SimpleHTMLFormatter(String preTag, String postTag)

自定义标记,前标记,后标记

//Hits

        Hits hs = searcher.search(q,Sort.RELEVANCE);

 

        //highlight

        SimpleHTMLFormatter shf = new SimpleHTMLFormatter("< >","</ >");

        Highlighter highlighter =new Highlighter(shf,new QueryScorer(q));

       

        SimpleFragmenter sf = new SimpleFragmenter(60);

        highlighter.setTextFragmenter( sf );

你可能感兴趣的:(String,filter,Lucene,iterator,query,文档)