Lucene的多域查詢、結果中查詢、查詢結果分頁、高亮查詢結果和結果評分


1.針對多個域的一次性查詢

1.1.三種方案    

    使用lucene構造搜索引擎的時候,如果要針對多個域進行一次性查詢,一般來說有三種方法:
    第一種實現方法是創建多值的全包含域的文本進行索引,這個方案最簡單。但是這個防范有個缺點:你不能直接對每個域的加權進行控制。
    第二種方法是使用MultiFieldQueryParser,它是QueryParser的子類,它會在后台程序中實例化一個QueryParser對象,用來針對每個域進行查詢表達式的解析,然后使用BooleanQuery將查詢結果合並起來。當程序向BooleanQuery添加查詢子句時,默認操作符OR被用於最簡單的解析方法中。為了實現更好的控制,布爾操作符可以使用BooleanClause的常量指定給每個域。如果需要指定的話可以使用BooleanClause.Occur.MUST,如果禁止指定可以使用BooleanClause.Occur.MUST_NOT,或者普通情況為BooleanClause.Occur.SHOULD。下面的程序展示的是如何創建MultiFieldQueryParser類的方法:
[java]   view plain copy
  1. // 在這四個域中檢索  
  2. String[] fields = { "phoneType""name""category""price" };  
  3. Query query = new MultiFieldQueryParser(Version.LUCENE_36, fields, analyzer).parse(keyword);  
    第三種方法就是使用高級DisjunctionMaxQuery類,它會封裝一個或者多個任意的查詢,將匹配的文檔進行OR操作。

1.2.方案選擇

    以上三種方案中,並不是第三種方案最好,也不是第一種方案就最差。哪種實現方式更適合你的應用程序呢?答案是“看情況”,因為這里存在一些取舍。全包含域是一個簡單的解決方案——但這個方案只能對搜索結果進行簡單的排序並且可能浪費磁盤空間(程序可能對同樣的文本索引兩次),但這個方案可能會獲得最好的搜索性能。
    MultiFieldQueryParser生成的BooleanQuery會計算所有查詢所匹配的文檔評分的總和(DisjunctionMaxQuery則只選取最大評分),然后它能夠實現針對每個域的加權。你必須對以上3中解決方案都進行測試,同時需要一起考慮搜索性能和搜索相關性,然后再找出最佳方案。
 

2.在結果中查詢

2.1.兩種方案

    在檢索結果中再次進行檢索,是一個很常見的需求,一般有兩種方案可以選擇:
    ①使用QueryFilter把第一個查詢當作一個過濾器處理;
    ②用BooleanQuery把前后兩個查詢結合起來,並且使用BooleanClause.Occur.MUST。
    針對第一種方法,我需要解釋一下。QueryFilter在Lucene的2.x版本中是存在的,但是在3.x中,lucene的API中這個類已經被廢棄了,無法再找到。如果你的項目使用的是lucene是3.x,但是你又一定要使用QueryFilter,那么你必須自己創建一個QueryFilter類,然后將2.x中QueryFilter的源代碼復制過來。你可能會說,直接在工程中同時使用lucene2.x和3.x的核心jar文件不就行了嗎。但遺憾的是,一個工程下,是不能同時使用不同版本的lucene的。

2.2.QueryFilter方案

上文已經說了,如果一定要使用QueryFilter,由於lucene2.x中沒有QueryFilter的API,所以自己要寫一個QueryFilter,QueryFilter的源代碼在lucene2.x中是這樣的:
[java]   view plain copy
  1. import org.apache.lucene.search.CachingWrapperFilter;  
  2. import org.apache.lucene.search.Query;  
  3. import org.apache.lucene.search.QueryWrapperFilter;  
  4.   
  5. public class QueryFilter extends CachingWrapperFilter {  
  6.   
  7.     /** 
  8.      * Constructs a filter which only matches documents matching 
  9.      * <code>query</code>. 
  10.      */  
  11.     public QueryFilter(Query query) {  
  12.         super(new QueryWrapperFilter(query));  
  13.     }  
  14.   
  15.     public boolean equals(Object o) {  
  16.         return super.equals((QueryFilter) o);  
  17.     }  
  18.   
  19.     public int hashCode() {  
  20.         return super.hashCode() ^ 0x923F64B9;  
  21.     }  
  22. }  
第一種方案的例子程序如下:
[java]   view plain copy
  1. //簡單實現對keyword的搜索  
  2.  public static void search(String keyword) throws IOException, ParseException {  
  3.          QueryParser queryParser = new QueryParser("content",new SimpleAnalyzer());  
  4.          Query query = queryParser.parse(keyword.trim());  
  5.          QueryFilter filter = new QueryFilter(query);  
  6.          //檢索  
  7.          search(query, filter);  
  8.  }  
  9.    
  10.  //在搜索oldKeyword的結果集中搜索newKeyword  
  11.  public static void searchInResult(String newKeyword, String oldKeyword) throws ParseException, IOException {                  
  12.          QueryParser queryParser = new QueryParser("content",new SimpleAnalyzer());  
  13.          Query query = queryParser.parse(newKeyword.trim());  
  14.          Query oldQuery = queryParser.parse(oldKeyword.trim());  
  15.          QueryFilter oldFilter = new QueryFilter(oldQuery);  
  16.          CachingWrapperFilter filter = new CachingWrapperFilter(oldFilter);  
  17.          //檢索  
  18.          search(query, filter);  
  19.  }  
  20.    
  21.  private static void search(Query query, Filter filter) throws IOException, ParseException {  
  22.          IndexSearcher ins = new IndexSearcher("d:/tesindex");  
  23.          Hits hits = ins.search(query, filter);  
  24.          for (int i = 0; i < hits.length(); i++) {  
  25.                  Document doc = hits.doc(i);  
  26.                  System.out.println(doc.get("content"));  
  27.          }  
  28.  }  

2.3.BooleanQuery方案

    使用BooleanQuery來實現在結果中檢索的過程是這樣的,首先通過關鍵字keyword1正常檢索,當用戶需要在檢索結果中再通過關鍵字keyword2檢索的時候,通過構建BooleanQuery,來實現對在結果中檢索的效果。這里要注意,這兩個關鍵字都要使用BooleanClause.Occur.MUST。
[java]   view plain copy
  1. //創建BooleanQuery  
  2. BooleanQuery booleanQuery = new BooleanQuery();  
  3. //多域檢索,在這四個域中檢索  
  4. String[] fields = { "phoneType""name""category","free" };  
  5. Query multiFieldQuery = new MultiFieldQueryParser(Version.LUCENE_36, fields, analyzer).parse(keyword);  
  6. //將multiFieldQuery添加到BooleanQuery中  
  7. booleanQuery.add(multiFieldQuery, BooleanClause.Occur.MUST);  
  8. //如果osKeyword不為空  
  9. if(osKeyword != null && !osKeyword.equals("") && !osKeyword.equals("null")){  
  10.     TermQuery osQuery = new TermQuery(new Term("phoneType",osKeyword));   
  11.     //將osQuery添加到BooleanQuery中  
  12.     booleanQuery.add(osQuery, BooleanClause.Occur.MUST);  
  13. }  

3.檢索結果分頁

3.1.兩種方案

    通過關鍵字的檢索,當lucene返回多條記錄的時候,往往一個頁面是無法容納所有檢索結果的,這自然而然就該分頁了。我這里給出兩種方案,這兩種方法我都是用過。
    第一種方法,就是講檢索結果全部封裝在一個Collection中,例如List中,將這個結果傳到前台,如jsp頁面。然后在這個list中進行分頁顯示;
    第二種方法,是使用lucene自帶的分頁工具public TopDocs topDocs(int start,int howMany)。
    我認為,第一種方法不涉及二次查詢,這樣的話就避免了在查詢上的浪費。但是當檢索的結果數據量很大,這樣一次性傳輸這么多數據到客戶端,而用戶檢索后得到的結果往往只會查看第一頁的內容,很少去查看第二頁、第三頁以及后面的內容,所以一次性將全部結果傳到前台,這樣的浪費是很大的。
    第二種方法,雖然每次翻頁都意味着一次查詢,表面上浪費了資源,但是由於lucene的高效,這樣的浪費對整個系統的影響是微乎其微的,但是這個方法避免了方法一中的缺陷。

3.2.分頁實現

[java]   view plain copy
  1. /** 
  2.      * 對搜索返回的前n條結果進行分頁顯示 
  3.      * @param keyWord       查詢關鍵詞 
  4.      * @param pageSize      每頁顯示記錄數 
  5.      * @param currentPage   當前頁  
  6.      */  
  7.     public void paginationQuery(String keyWord,int pageSize,int currentPage) throws ParseException, CorruptIndexException, IOException {  
  8.         String[] fields = {"title","content"};  
  9.         QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_36,fields,analyzer);  
  10.         Query query = queryParser.parse(keyWord);  
  11.            
  12.         IndexReader indexReader  = IndexReader.open(directory);  
  13.         IndexSearcher indexSearcher = new IndexSearcher(indexReader);  
  14.            
  15.         //TopDocs 搜索返回的結果  
  16.         TopDocs topDocs = indexSearcher.search(query, 100);//只返回前100條記錄  
  17.         int totalCount = topDocs.totalHits; // 搜索結果總數量  
  18.         ScoreDoc[] scoreDocs = topDocs.scoreDocs; // 搜索返回的結果集合  
  19.            
  20.         //查詢起始記錄位置  
  21.         int begin = pageSize * (currentPage - 1) ;  
  22.         //查詢終止記錄位置  
  23.         int end = Math.min(begin + pageSize, scoreDocs.length);  
  24.            
  25.         //進行分頁查詢  
  26.         for(int i=begin;i<end;i++) {  
  27.             int docID = scoreDocs[i].doc;  
  28.             Document doc = indexSearcher.doc(docID);  
  29.             int id = NumericUtils.prefixCodedToInt(doc.get("id"));  
  30.             String title = doc.get("title");  
  31.             System.out.println("id is : "+id);  
  32.             System.out.println("title is : "+title);  
  33.         }     
  34.     }  

4.高亮檢索結果

    針對檢索結果的高亮實現方法,在lucene中提供了響應的工具,這里使用lucene-highlighter-3.6.2.jar來實現對檢索結果的高亮顯示。
[java]   view plain copy
  1. public void search(String fieldName, String keyword)throws CorruptIndexException, IOException, ParseException {  
  2.     searcher = new IndexSearcher(indexPath);  
  3.     QueryParser queryParse = new QueryParser(fieldName, analyzer); // 構造QueryParser,解析用戶輸入的檢索關鍵字  
  4.     Query query = queryParse.parse(keyword);  
  5.     Hits hits = searcher.search(query);  
  6.     for (int i = 0; i < hits.length(); i++) {  
  7.         Document doc = hits.doc(i);  
  8.         String text = doc.get(fieldName);  
  9.         SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'>""</font>");  
  10.         Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));  
  11.         highlighter.setTextFragmenter(new SimpleFragmenter(text.length()));  
  12.         if (text != null) {  
  13.             TokenStream tokenStream = analyzer.tokenStream(fieldName,new StringReader(text));  
  14.             String highLightText = highlighter.getBestFragment(tokenStream,text);  
  15.             System.out.println("高亮顯示第 " + (i + 1) + " 條檢索結果如下所示:");  
  16.             System.out.println(highLightText);  
  17.         }  
  18.     }  
  19.     searcher.close();  
  20. }  
上文的一行判斷語句很重要:if(text != null),如果text為空,那么顯示結果不但沒有被高亮,而且得到的原始結果也會被過濾。可以再代碼中加上,如果text==null,則讓將原始檢索結果賦給text,從而將結果顯示出來。
 

5.檢索結果的評分

    lucene的評分是有一套自己的機制的,輸入某一個關鍵字,lucene會對命中的記錄進行評分,默認情況下,分數越高的結果會排在結果的越前面。如果在創建索引的時候,沒有對某個域進行加權,那么默認分數的上限是5分,如果有對域做加權,檢索結果的評分可能會出現大於5分的情況。
    我們可以使用explain()來看看lucene對檢索結果的評分情況:
[java]   view plain copy
  1. //評分  
  2. Explanation explanation = indexSearcher.explain(query, docID);  
  3. System.out.println(explanation.toString());  
在后台打印出來的信息如下:
[plain]   view plain copy
  1. 2.4342022 = (MATCH) weight(name:books in 71491), product of:  
  2.        0.2964393 = queryWeight(name:books), product of:  
  3.          8.21147 = idf(docFreq=109, maxDocs=149037)  
  4.          0.036100637 = queryNorm  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM