一.使用步驟
1 //將一個字符串創建成token流,第一個參數---fiedName,是一種標志性參數,可以寫空字符串,不建議用null,因為null對於IKAnalyzer會包錯
2 TokenStream tokenStream = new IKAnalyzer().tokenStream("keywords",new StringReader("思想者")); 3 //添加單詞信息到AttributeSource的map中
4 CharTermAttribute attribute = tokenStream.addAttribute(CharTermAttribute.class); 5 //重置,設置tokenstream的初始信息
6 tokenStream.reset(); 7 while(tokenStream.incrementToken()) {//判斷是否還有下一個Token
8 System.out.println(attribute); 9 } 10 tokenStream.end(); 11 tokenStream.close();
二.代碼與原理分析
TokenStream用於訪問token(詞匯,單詞,最小的索引單位),可以看做token的迭代器
1.如何獲得TokenStream流 ---->對應第一行代碼
先獲得TokenStreamComponents,從他獲得TokenStream(TokenStreamComponents內部封裝了一個TokenStream以及一個Tokenizer,關於Tokenizer下面會具體講)
可以看到主要是通過reuseStrategy對象獲得TokenStreamComponents,然后通過components對象獲得所需的TokenStream流.reuseStrategy對象即為"復用策略對象",但是如何獲得此對象?
通過下面這兩張圖可以看到在Analyzer的構造器中通過傳入GLOBAL_REUSE_STRATEGY對象完成了reuseStrategy對象的初始化.
ReuseStrategy是Analyzer的一個靜態內部類,同時他也是一個抽象類,其子類也是Analyzer的內部類,上面提到了GLOBAL_REUSE_STRATEGY,這個對象是通過匿名類的方式繼承了ReuseStrategy獲得
的(第三張圖)
當然你會說Analyzer不是抽象類嗎?他的構造器怎么被調用?因為我們用到的各種分詞器,如IKAnalyzer,StandAnalyzer都是Analyzer的子類或間接子類,new一個分詞器對象時會調用父類分詞器的構造器
接下來就是 reuseStrategy.getReusableComponents(this, fieldName);通過上面的分析,發現調用的getReusableComponents方法就是調用GLOBAL_REUSE_STRATEGY對象里的getReusableComponents
這個方法又把我們傳入的分詞器對象傳遞給ReuseStrategy類里的getStoredValue方法,最后通過storedValue(老版本里叫做tokenStreams)獲得TokenStreamComponents對象(儲存在ThreadLocal中)
可以看到查找的時候根本沒用到fieldname,這也是為什么說new xxxAnalyzer().tokenStream()時第一個參數filedname可以寫空字符串的原因
關於CloseableThreadLocal做個簡略的說明:
CloseableThreadLocal是lucene對java自帶的的ThreadLocal的優化,解決了jdk中定期執行無效對象回收的問題
請參考:https://www.cnblogs.com/jcli/p/talk_about_threadlocal.html
既然獲得了TokenStreamComponents對象,接下來初始化Reader對象,事實上這個初始化就是把你傳入的reader對象返回過來,
接着往下走判斷components對象是否為空?為null則新創建一個components對象,Analyezer的createComponents是個抽象方法,我們以其子類IKAnalyzer中的此方法作為解析
這里又出現了一個新的類Tokenizer,Tokenizer說白了也是一個TokenStream,但是其input是一個Reader,這意味着Tokenizer是對字符操作,換句話說由Tokenizer來進行分詞,即生成token
TokenStream還有一個重要的子類叫做TokenFilter,其input是TokenStream,也就是說他負責對token進行過濾,如去除標點,大小寫轉換等,從上面貼的IKAnalyzer的createComponents方法看不到TokenFilter的影子
貼下標准分詞器里的代碼
到這TokenStreamComponents對象創建完成了,大體的流程是先檢查復用策略對象里有沒有現成的components對象可用,有的話直接拿過來用,沒有的話再去創建,創建完成
后加入復用策略對象里,以便下次使用.獲得了TokenStreamComponents就可以獲得TokenStream對象
組合不同的Tokenizer和TkoenFilter就變成了不同的Analyzer
2.AttributeImpl與AttributeSource -------->對應第二行代碼
AttributeImpl是Attribute接口的實現類,簡單的說AttributeImpl用來存儲token的屬性,主要包括token的文本,位置,偏移量.位置增量等信息,比如CharTermAttribute保存文本
OffsetAttribute保存偏移量,PositionIncrementAttribute用於保存位置增量,而AttributeSource又包含了一系列的AttributeImpl,換句話說AttributeSource是token的屬性集合,
AttributeSource內部有兩個map.分別定義了從xxxAttribute---->AttributeImpl,xxxAttributeImpl----->AttributeImpl兩種映射關系,這樣保證了xxxAttributeImpl對於同一個AttributeSource只有一個實例
所以需要什么信息,就用tokenStream.addAttribute(xxx.Class)即可
//添加單詞信息到AttributeSource的兩個map中
CharTermAttribute attribute = tokenStream.addAttribute(CharTermAttribute.class);
3.reset
lucene各個版本的API變化很大,網上好多的資料都有些過時了,比如 TokenStream contract violation: reset()/close() call missing這個異常是
因為在調用incrementToken()方法前沒有調用reset()方法,一些老版本不需要調用,然而現在高版本的lucene必須先調用reset()把used屬性設置為
false后才能執行incrementToken()
重置的目的是為了讓告訴incrementToken(),此流未被使用過,要從流的開始處返回詞匯
4.incrementToken()
是否還有下一個詞匯
5.end()與close()
end()調用endAttribute(),把termlength設置為0,意思是沒有詞匯了為close()做准備