文章目錄
官方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數組,根據起始位置和長度來獲取當前的單詞。那麼,其實完成分詞的關鍵就是計算zzStartRead
和zzStartRead
。
這個兩個成員變量的計算,跟蹤源碼,發現是在scanner.getNextToken()
這裏實現的,也就是StandardTokenizerImpl
實現類的getNextToken
方法了。所以,最關鍵的分詞算法就是在這裏實現,我們可以根據自己的實際情況設計自己的分詞算法。
最後,由於根據上面提到的標準分詞器StandardAnalyzer
還使用忽略大小寫和停用詞的功能,所以,StandardTokenizerImpl
分詞之後的單詞還會進行大小寫處理和停用詞過濾。
歡迎關注同名公衆號:“我就算餓死也不做程序員”。
交個朋友,一起交流,一起學習,一起進步。