Lucene源碼(一):分詞器的底層原理

官方Demo

我們先看官方提供的demo代碼,從使用demo運行一遍,看看分詞之後的結果,然後再對源碼進行研究。分詞的核心代碼其實就是這幾句:

Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser(field, analyzer);
Query query = parser.parse(line);

代碼運行後,我們發現分詞的結果存儲在變量query 。

Query

Query對應實現類BooleanQuery。分詞之後的結果存儲於變量clauses(Collections)中,裏面的元素就是分詞之後的每個單詞即BooleanClause實例。

BooleanClause類有一個變量爲query(TermQuery)–>term(Term),這個term就是我們的單詞了。
在這裏插入圖片描述

Term

field:索引字段
bytes:單詞字符串對應的byte[]

StandardAnalyzer

官方的標準分詞器StandardAnalyzer,它使用了標準分詞器對應的分詞算法StandardTokenizer,然後再加上忽略大小寫LowerCaseFilter和停用詞StopFilter的過濾功能TokenFilter

@Override
  protected TokenStreamComponents createComponents(final String fieldName) {
    final Tokenizer src;
    if (getVersion().onOrAfter(Version.LUCENE_4_7_0)) {
      StandardTokenizer t = new StandardTokenizer();
      t.setMaxTokenLength(maxTokenLength);
      src = t;
    } else {
      StandardTokenizer40 t = new StandardTokenizer40();
      t.setMaxTokenLength(maxTokenLength);
      src = t;
    }
    TokenStream tok = new StandardFilter(src);
    tok = new LowerCaseFilter(tok);
    tok = new StopFilter(tok, stopwords);
    return new TokenStreamComponents(src, tok) {
      @Override
      protected void setReader(final Reader reader) {
        int m = StandardAnalyzer.this.maxTokenLength;
        if (src instanceof StandardTokenizer) {
          ((StandardTokenizer)src).setMaxTokenLength(m);
        } else {
          ((StandardTokenizer40)src).setMaxTokenLength(m);
        }
        super.setReader(reader);
      }
    };
  }

源碼分析

QueryParser parser = new QueryParser(field, analyzer);
Query query = parser.parse(line);

QueryParser.parse是繼承QueryParserBase,源碼如下:在這裏插入圖片描述
new FastCharStream(new StringReader(query)),應該就是把字符串轉化爲lucene優化過的字節流了;ReInit是重新初始化QueruParser

接下來是TopLevelQuery,然後看關鍵代碼,我們可以直接跳到QueryBuilder.createFieldQuery
在這裏插入圖片描述

QueryBuilder.createFieldQuery

QueryBuilder這個實現類中,createFieldQuery會生成帶有分詞結果的Query對象。

爲了方便理解,我們可以先看代碼執行後,CachingTokenFilter會緩存分詞結果,放在一個cache鏈表,裏面的每個元素就是經過分詞之後的每個單詞。
在這裏插入圖片描述
最後,再把stream緩存的每一個單詞轉化爲對應一個Query實例,這個方法就不細說了,有興趣的可以自己去看源碼。
在這裏插入圖片描述
analyzeMultiBoolean這個方法其實就是如何將CachingTokenFilter的分詞結果傳輸到BooleanQuery

-------------------------------繼續看源碼----------------------------------

首先,看第一部分代碼,都是一些初始化,包括分詞器Analyzer,還有緩存分詞結果的CachingTokenFilter對象stream變量
在這裏插入圖片描述
然後,第二部分代碼,stream.incrementToken()這是分詞的重點,進入這個方法
在這裏插入圖片描述
進入了之後,我們可以看到,像我們上面看到的,會有一個cache鏈表來存儲分詞結果。然後,再轉化爲一個iterator
在這裏插入圖片描述
那麼,顯而易見,分詞是在fillCache()這個方法塊裏完成的了。
在這裏插入圖片描述

StandardTokenizer

再深入研究,發現來到了StandardTokenizer類,這裏我們需要知道scanner是實現類StandardTokenizerImpl,我們先看scanner.getText(termAtt)方法
在這裏插入圖片描述

StandardTokenizerImpl

scanner.getText(termAtt)這裏的變量t,即傳入的參數termAtt有一個屬性termBuffer,是一個char[],看名字也就可以知道跟Term是類似的作用,存放單詞的。可以看到,方法的功能是從另一個char數組,根據起始位置和長度來獲取當前的單詞。那麼,其實完成分詞的關鍵就是計算zzStartReadzzStartRead
在這裏插入圖片描述
這個兩個成員變量的計算,跟蹤源碼,發現是在scanner.getNextToken()這裏實現的,也就是StandardTokenizerImpl實現類的getNextToken方法了。所以,最關鍵的分詞算法就是在這裏實現,我們可以根據自己的實際情況設計自己的分詞算法。

最後,由於根據上面提到的標準分詞器StandardAnalyzer還使用忽略大小寫和停用詞的功能,所以,StandardTokenizerImpl分詞之後的單詞還會進行大小寫處理和停用詞過濾。
在這裏插入圖片描述
在這裏插入圖片描述
歡迎關注同名公衆號:“我就算餓死也不做程序員”。
交個朋友,一起交流,一起學習,一起進步。在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章