Lucene搜索流程(4.Query)

最進因爲工做的事和國慶,回家了沒有環境來寫文章,因此擱置了很久,人一懶就不想動,其中有人催我,想一想也不能半途而廢了,因爲以前也寫了些草稿,決心今天必定要寫了這玩意。閒話很少說,開始介紹Lucene的查詢。數據庫

若是將整個Lucene系統當作一個數據庫系統也勉強說得過去,由於它擁有完善的存儲系統和文件體系結構,Query相對於Lucene來講便是這個數據庫Sql,是獲取數據的一個約束,如何可以根據業務來獲取數據就須要構建相應的查詢條件對索引中得數據進行過濾,獲取到咱們的數據,這裏主要是說下Lucene的幾種查詢。架構

先來看下Query這個東西,在Lucene Java中,它定義爲一個抽象類,其餘的各類具體查詢都是繼承自它或者它的子類,它裏面有幾個重要的方法:app

 

  /** Expert: called to re-write queries into primitive queries. For example,oop

   * a PrefixQuery will be rewritten into a BooleanQuery that consists學習

   * of TermQuerys.測試

   */this

  public Query rewrite(IndexReader reader) throws IOException {搜索引擎

    return this;spa

  }翻譯

講這個方法要聯繫之前咱們說過的倒排索引的知識,Lucene之因此可以快速查找到某些包含特殊詞的文檔是由於有了倒排索引,倒排索引的結構是一個詞對應着多個文檔的一個鏈表,所以若是咱們知道一個詞的話就能很快查找到,可是這種結構確不適用於模糊查找,說到這裏我想說個事,在我作搜索引擎的時候,那些測試的人總是:哎呀呀,曾傑,你這個搜索引擎有問題啊,你看我輸入一個字沒把全部包含這個字的數據給我找出來啊。遇到這樣的狀況我先是滿頭黑線,而後在解釋說這個搜索引擎並非同數據庫的like。。。。題外話了。那若是的確有這種狀況怎麼辦呢?Lucene早就想到了,提供了一個WildcardQuery,能夠根據這個查詢提供的詞先模糊匹配出全部包含這個字符的Term,而後根據這些Term去查找指定的Document,其中根據這個查詢提供的詞先模糊匹配出全部包含這個字符的Term這一步的實現就是要靠這方法來實現,經過這個方法能夠將一些查詢轉換成指定的Term的查詢,至於怎麼實現咱們在下面再細說。那在何時Lucene會調用這個方法呢?在調用IndexSearcher. search(Query query, int n)的時候最終會調用createNormalizedWeight(query)這個放方法,Searcher的默認實現裏面便會經過調用rewrite來重寫查詢。

  /**

   * Expert: Constructs an appropriate Weight implementation for this query.

   *

   * <p>

   * Only implemented by primitive queries, which re-write to themselves.

   */

  public Weight createWeight(Searcher searcher) throws IOException {

    throw new UnsupportedOperationException("Query " + this + " does not implement createWeight");

  }

這個方法也是個核心的方法,一個查詢最後獲取的數據就是經過這個方法先建立一個Weight(權重),這個東西能簡單來講就是一個計算這個查詢的得分權重,而且能返回一個匹配到的文檔的枚舉的對象,Lucene在查詢的時候會調用這個方法自動生成一個Weight而後從Weight中獲取一個匹配到的文檔的枚舉器Scorer,而後用Weight對這些文檔進行打分,最後返回給用戶,其重要性就不言而喻了吧。Weight的具體工做方法咱們之後再細說。

  /** Sets the boost for this query clause to <code>b</code>.  Documents

   * matching this clause will (in addition to the normal weightings) have

   * their score multiplied by <code>b</code>.

   */

  public void setBoost(float b) { boost = b; }

 

  /** Gets the boost for this clause.  Documents matching

   * this clause will (in addition to the normal weightings) have their score

   * multiplied by <code>b</code>.   The boost is 1.0 by default.

   */

  public float getBoost() { return boost; }

最後這兩個方法就是很經常使用的開發API了,就是設置和獲取查詢權重,這個查詢權重簡單的描述了這個查詢在本次查詢中得重要性,重要性越高,這個查詢匹配到的文檔得分天然越高。

Query介紹完了就介紹下他的子類。

Query的子類大致上能夠分爲三種,TermQuery BooleanQueryMultiTermQuerySpanQuery,還有一些比較特殊的查詢可是偶爾也會用到的查詢我這裏也會介紹。

TermQuery能夠說是Lucene中一種最底層也是最廣泛的的查詢了,它就是簡單的實現了倒排索引的概念,經過一個詞來獲取包含這個詞的文檔,TermQuery的重寫方法rewrite就是簡單的返回了this,由於這個是最底層的查詢了,天然不須要加工了。一個TermQuery經過一個Term來構造,即查詢出包含這個Term的相關文檔,這種概念理解起來仍是很容易的。

BooleanQuery:其實Lucene的查詢邏輯既是現實了一個查詢樹的概念,經過查詢樹來對數據進行層層過濾就能獲得咱們想要的數據,BooleanQuery至關於這個書中的一個個節點,但絕對不是葉子節點,最終的查詢功能仍是得靠具體的葉子節點去實現的,往一個節點中添加一個子節點的代碼爲:

  public void add(Query query, BooleanClause.Occur occur) {

    add(new BooleanClause(query, occur));

  }

其中occur爲一個邏輯描述枚舉,這三個值爲SHOULD(能夠知足也能夠不知足,但不是必要條件,至關於OR)、MUST(數據必須知足此條件,至關於AND)、MUST_NOT(數據必須不在此條件範圍中,至關於NOT),這些枚舉將每一個子查詢的匹配要求獨立開來匹配數據,而後在經過邏輯來進行組合,最後就是咱們但願獲得的數據了。

BooleanQueryrewrite幹得事情也是相對較簡單的,即循環將調用子查詢的rewrite方法進行重寫,這應該算是一種遞歸的實現。

 

最後介紹的是MultiTermQuery,這個是Lucene裏面最複雜的一種查詢了,其中的變種也是最多的,也正是經過它實現了Lucene的強大的查詢功能。

MultiTermQuery有幾個方法來實現多詞查詢

  /** Construct the enumeration to be used, expanding the pattern term. */

  protected abstract FilteredTermEnum getEnum(IndexReader reader)

      throws IOException;

這個方法便是獲取重寫後的一個詞的枚舉器,這個方法是經過在MultiTermQuery中定義的一個內部靜態類RewriteMethod中調用的,其實MultiTermQuery的重寫是經過RewriteMethod這個類來進行的,經過調用RewriteMethod.rewrite(IndexReader reader, MultiTermQuery query)來重寫這個查詢,通常的實現爲在RewriteMethod中將FilteredTermEnum中得多個詞組合成一個BooleanQuery,這些條件的組合邏輯爲SHOULD

其中FilteredTermEnum相對於普通的TermEnum來講多了幾個方法

用來比較一個詞是不是當前匹配到的,若是是的返回true不然返回false,若是返回false則同時表明這個迭代器到了末端

    protected abstract boolean termCompare(Term term);

   

    //這個是用來定義一個匹配到的詞的相關性(這個地方是根據註釋翻譯出來的,具體做用應該是定義若是一個詞被匹配到而後擁有的權重)

    public abstract float difference();

 

    //這個方法用來獲取當前枚舉是否已經到了末端的標示

    protected abstract boolean endEnum();

 

經過這幾個方法就能夠將多個詞重寫出來而後再經過實際的TermQuery進行最終查詢。

 

若是想要了解更多關於MultiTermQuery建議先看看WildCardQuery的源碼,是比較簡單和明瞭的。

 

SpanQuery實際上是Query的一個特殊變種,它的子類能夠經過對他的查詢進行一個詞的位置運算,全部的SpanQuery的重寫都是返回自己,由於都是精確查詢,並且全部的SpanQuerycreateWeight都是返回一個SpanWeight,而且都重寫了

 

  public abstract Spans getSpans(IndexReader reader) throws IOException;

這個方法,用來獲取一個獲取查詢到的全部詞在指定包含詞的文檔中得位置信息,固然咱們再保存索引的時候必須將指定查詢的字段的TermVector設置爲WITH_POSITIONS_OFFSETS才能獲取到位置,而且進行計算,經常使用的子類有SpanNearQuery,這個查詢包含一系列的子查詢,能夠經過參數指定這些子查詢在某個文檔中匹配到的詞之間的最大距離,而且能夠指定匹配順序必須跟查詢順序同樣,並且匹配的詞的距離越小則得分越高,因此這些詞之間是AND查詢,這對於一些精確度比較高的場景仍是很是有用的。

 

 

最後來介紹2個有用的查詢:

一個是MatchllDocsQuery,這個查詢功能很簡單也很實用,就是能匹配索引中所有的文檔,當須要查詢索引中得所有的文檔的時候,之前我使用過一種方法,就是在全部文檔中添加一個靜態域,可是發現有這個查詢後我才知道那樣作徹底是多餘的。

還有一個是PayLoadTermQuery,這個查詢可以獲取指定字段保存的PayLoad信息,而且經過SimilarityscorePayload方法來根據PayLoad來影響文檔的得分,關於PayLoad的相關信息,網上有不少文章,之後我也會介紹。

 

OK我在這裏只是簡單介紹一個Query的查詢工做和一些簡單的查詢類型分類,若是有興趣能夠去把Lucene的源碼下下來而且好好研究下,我這裏說的只是Lucene強大查詢功能的冰山一角,還有最近在看Hadoop的東西,發現一我的寫的東西不管是在代碼風格仍是架構風格上都很類似,很容易上手。

最後推薦一個深層次Lucene學習博客,其實這個博客也是從基礎講起,可是要學習須要一點點的耐心,很佩服那位博主http://forfuture1978.iteye.com/blog/  繼續向他學習

相關文章
相關標籤/搜索