lucene 7.x 分詞 TokenStream的使用及源碼分析


一.使用步驟

 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()做准備

 


免責聲明!

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



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