Lucene.net(4.8.0) 學習問題記錄二: 分詞器Analyzer中的TokenStream和AttributeSource


前言:目前自己在做使用Lucene.net和PanGu分詞實現全文檢索的工作,不過自己是把別人做好的項目進行遷移。因為項目整體要遷移到ASP.NET Core 2.0版本,而Lucene使用的版本是3.6.0 ,PanGu分詞也是對應Lucene3.6.0版本的。不過好在Lucene.net 已經有了Core 2.0版本,4.8.0 bate版,而PanGu分詞,目前有人正在做,貌似已經做完,只是還沒有測試~,Lucene升級的改變我都會加粗表示。

Lucene.net 4.8.0   

https://github.com/apache/lucenenet

PanGu分詞(可以直接使用的)

https://github.com/SilentCC/Lucene.Net.Analysis.PanGu

 JIEba分詞(可以直接使用的)

https://github.com/SilentCC/JIEba-netcore2.0

 

 

Lucene.net 4.8.0 和之前的Lucene.net 3.6.0 改動還是相當多的,這里對自己開發過程遇到的問題,做一個記錄吧,希望可以幫到和我一樣需要升級Lucene.net的人。我也是第一次接觸Lucene ,也希望可以幫助初學Lucene的同學。

 

一,Analyzer 中的TokenStream 

1.TokenSteam的產生

在這篇博文中,其實已經介紹了TokenStream 是怎么產生的:

 http://www.cnblogs.com/dacc123/p/8035438.html

在Analyzer 中,同一個線程上的所有Analyzer實例都是共用一個TokenStream,而實現如此都是因為Analyzer類中 storedValue 是全局共用的,獲取TokenStream的方法是由reuseStrategy 類提供的,TokenStream 繼承自AttributeSource

那么TokenStream的作用什么呢?

2.TokenSteam的使用

TokenStream 實際上是由一系列Token(分詞)組合起來的序列,這里僅僅介紹如何通過TokenStream獲得分詞的信息。TokenStream的工作流程:

    1. 創建TokenStream

    2.TokenStream.Reset()

    3.TokenStream.IncrementToken()

    4.TokenStream.End();

    5.TokenStream.Dispose() //Lucene 4.8.0中已經取消了Close(),只有Dispose()

在執行:

_indexWriter.AddDocument(doc)

之后,IndexWriter則會調用初始化時創建的Analyzer,也即IndewWriterConfig()中的Analyzer參數。這里以PanGu分詞為例子。

調用分詞器,首先會執行CreateComponents()函數,創建一個TokenStreamComponents,這也是為什么所有自定義,或者外部的分詞器如果繼承Analyzer,必須要覆寫CreateComponents()函數:

  protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
        {
            var result = new PanGuTokenizer(reader, _originalResult, _options, _parameters);
            var finalStream = (TokenStream)new LowerCaseFilter(LVERSION.LUCENE_48, result);

          
            finalStream.AddAttribute<ICharTermAttribute>();
            finalStream.AddAttribute<IOffsetAttribute>();

            return new TokenStreamComponents(result, finalStream);
        }

可以看到在這個CreateComponents函數中,我們可以初始化創建自己想要的Tokenizer和TokenStream。TokenStreamComponents是Lucene4.0中才有的,一個TokenStreamComponents是由Tokenizer和TokenStream組成。

在初始化完TokenStream 之后我們可以添加屬性Attribute 到TokenStream中:

finalStream.AddAttribute<ICharTermAttribute>();
finalStream.AddAttribute<IOffsetAttribute>();

  2.1 AttributeSource的介紹

  上面說到TokenStream 繼承自AttributeSource , finalStream.AddAttribute<ICharTermAttribute> 真是調用了父類AttributeSource的方法AddAttribute<T>() ,所以AttributeSoucre是用來給TokenStream添加一系列屬性的,這是Lucene4.8.0中AttributeSource中AddAttribute的源碼:

  

  public T AddAttribute<T>()
            where T : IAttribute
        {
            var attClass = typeof(T);
            if (!attributes.ContainsKey(attClass))
            {
                if (!(attClass.GetTypeInfo().IsInterface && typeof(IAttribute).IsAssignableFrom(attClass)))
                {
                    throw new ArgumentException("AddAttribute() only accepts an interface that extends IAttribute, but " + attClass.FullName + " does not fulfil this contract.");
                }
          //正真添加Attribute的函數,而創造Attribute實例則是通過AttributeSource中的 
          //private readonly AttributeFactory factory; AddAttributeImpl(
this.factory.CreateAttributeInstance<T>()); } T returnAttr; try { returnAttr = (T)(IAttribute)attributes[attClass].Value; } #pragma warning disable 168 catch (KeyNotFoundException knf) #pragma warning restore 168 { return default(T); } return returnAttr; }

 2.2 Attribute介紹

    上面介紹了AttributeSource 給TokenStream添加屬性Attribute ,其實Attribute就是你需要獲得的分詞的屬性。

    比如:上面寫到的 ICharTermAttribute 繼承自CharTermAttribute 表示的是分詞內容;

       IOffsetAttribute 繼承自 OffsetAttribute 表示的是分詞起始位置和結束位置;

    類似的還有 IFlasAttribute , IKeywordAttribute,IPayloadAttribute,IPositionIncrementAttribute,IPositionLengthAttribute,ITermToBytesRefAttribute,ITypeAttribute

    我們再看Token(分詞)類的源碼:

    

  public class Token : CharTermAttribute, ITypeAttribute, IPositionIncrementAttribute, IFlagsAttribute, IOffsetAttribute, IPayloadAttribute, IPositionLengthAttribute

   

    其實Token(分詞),是繼承這些Attribute,也就是說分詞是由這些屬性組成的,所以就可以理解為什么在TokenStream中添加Attributes。

    

再回到之前,再初始化TokenStream 和添加完屬性之后,必須執行TokenStream的Reset(),才可繼續執行TokenStream.IncrementToken().

Reset()函數實際上在TokenStream創建和使用之后進行重置,因為我們之前說過,在Analyzer中所有實例是共用一個TokenStream的所以在TokenStream被使用過一次后,需要Reset() 以清除上次使用的信息,重新給下一個需要分詞的text使用。

而IncrementToken實際的作用則是在遍歷TokenStream 中的Token,類似於一個迭代器。

  public sealed override bool IncrementToken()
        {
            ClearAttributes();
            Token word = Next();
            if (word != null)
            {
                var buffer = word.ToString();
                termAtt.SetEmpty().Append(buffer);
                offsetAtt.SetOffset(word.StartOffset, word.EndOffset);
                typeAtt.Type = word.Type;
                return true;
            }
            End();
            this.Dispose();
            return false;
        }

直到返回的false ,表示分詞已經遍歷完了,這個時候調用End() 和Dispose() 來注銷這個TokenStream。在這個過程中,TokenStream是可以被使用多次的,比如我寫入索引的時候,加入兩個Field : 

new Field("title","xxxx")
new Field("content","xxxxx")

對這個兩個域進行分詞,TokenStream創建之后,會先對title進行分詞,遍歷。然后執行Reset(),再對content進行分詞,遍歷。直到所有要分詞的域都遍歷過了。才會執行End()和Dispose()函數進行銷毀。

 

二,問題:搜索不到內容

  在遷移的過程中,突然出現了搜索不到內容的bug,經過調試,發現寫索引的時候,對文本的分詞都是正確。這里要提一點,分詞(Token) 和 Term的區別 ,term是最小的搜索的單位,就是每個詞語,比如“我是搞IT的”,那么,經過分詞 “我”,“是”,“搞”,“IT” 這些都是term,而這些分詞的具體信息,比如起始位置信息,都包含在Token當中,在Lucene2.9中之后,已經不推薦用Token(分詞),而直接用Attribute表示這些term的屬性 

      后來發現寫索引的時候正常,但是在搜索的時候,獲取搜索關鍵詞是,利用自己寫的TokenStream獲取分詞信息出了錯。

  

    tokenStream.Reset();
            //ItermAttribute在Lucene4.8.0中已經替換為CharTermAttribute
            while (tokenStream.IncrementToken())
            {
                
                var termAttr = tokenStream.GetAttribute<ICharTermAttribute>();
                var str = new string(termAttr.Buffer, 0, termAttr.Buffer.Length);
                var positionAttr = tokenStream.GetAttribute<IOffsetAttribute>();
                var start = positionAttr.StartOffset;
                var end = positionAttr.EndOffset;
                yield return new Token() { EndPosition = end, StartPosition = start, Term = str };
            }
           

termAttr.Buffer  是字節數組,而termAttr.Buffer.Length 是字節數組的長度,是固定。而termAttr.Length 是字節數組中實際元素的長度,是不一樣的。我那樣寫會導致得到term字節信息是 [69,5b,23,/0,/0,/0,/0,/0,/0,/0] 因為長度填錯了,所以后面自動填充/0,這樣自然搜索不到,改成termAttr.Length就可以了。

這里在提一下在Lcuene.net 4.0中新增了BytesRef 類,表示term的字節信息,以后會介紹道

 


免責聲明!

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



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