天天看點

Lucene的分析過程 - HackerVirus

Lucene的分析過程

Lucene的分析過程

回顧反向索引的建構

Lucene的分析過程 - HackerVirus
  1. 收集待建索引的原文檔(Document)
  2. 将原文檔傳給詞條化工具(Tokenizer)進行文本詞條化
  3. 将第二步得到的詞條(Token)傳給語言分析工具(Linguistic modules)進行語言學預處理,得到詞項(Term)
  4. 将得到的詞項(Term)傳給索引元件(Indexer),建立反向索引

注:詳細文檔->反向索引的理論過程見詞項詞典及倒排記錄表

分析操作的使用場景

1.如上,反向索引的建構階段

2.針對自由文本的查詢階段

QueryParser parser = new QueryParser(Version.LUCENE_36, field, analyzer);

Query query = parser.parse(queryString);

lucene的Analyzer接收表達式queryString中連續的獨立的文本片段,但不會接收整個表達式。

例如:對查詢語句"president obama" + harvard + professor,QueryParser會3次調用分析器,首先是處理文本“president obama”,然後是文本“harvard”,最後處理“professor”。

3.搜尋結果中高亮顯示被搜尋内容時(即結果摘要-Snippets的生成),也可能會用到分析操作

剖析lucene分析器

抽象類Analyzer

Analyzer類是一個抽象類,是所有分析器的基類。

其主要包含兩個接口,用于生成TokenStream(所謂TokenStream,後面我們會講到,是一個由分詞後的Token 結果組成的流,能夠不斷的得到下一個分成的Token。)。

接口:

1.TokenStream tokenStream(String fieldName, Reader reader)

2.TokenStream reusableTokenStream(String fieldName, Reader reader)

為了提高性能,使得在同一個線程中無需再生成新的TokenStream 對象,老的可以被重用,是以有reusableTokenStream 一說。

Analyzer 中有CloseableThreadLocal<Object> tokenStreams =newCloseableThreadLocal<Object>(); 成員變量, 儲存目前線程原來建立過的TokenStream , 可用函數setPreviousTokenStream 設定,用函數getPreviousTokenStream 得到。在reusableTokenStream 函數中,往往用getPreviousTokenStream 得到老的TokenStream 對象,然後将TokenStream 對象reset 一下,進而可以重新開始得到Token 流。

抽象類ReusableAnalyzerBase

ReusableAnalyzerBase extendsAnalyzer,顧名思義主要為tokenStream的重用。

其包含一個接口,用于生成TokenStreamComponents。

接口:

TokenStreamComponents createComponents(String fieldName,Reader reader);

reusableTokenStream的實作代碼分析:

public final TokenStream reusableTokenStream(final String fieldName,
      final Reader reader) throws IOException {
    // 得到上一次使用的TokenStream
    TokenStreamComponents streamChain = (TokenStreamComponents)getPreviousTokenStream();
    final Reader r = initReader(reader);
    //如果沒有PreviousTokenStream則生成新的, 并且用setPreviousTokenStream放入成員變量,使得下一個可用。
    //如果上一次生成過TokenStream,則reset。reset失敗則生成新的。
    if (streamChain == null || !streamChain.reset(r)) {
      streamChain = createComponents(fieldName, r);
      setPreviousTokenStream(streamChain);
    }
    return streamChain.getTokenStream();
  }
      

内部static類TokenStreamComponents

簡單封裝輸入Tokenizer和輸出TokenStream。

最簡單的一個Analyzer:SimpleAnalyzer

SimpleAnalyzer extendsReusableAnalyzerBase,實作createComponents方法。TokenStream的處理是将字元串最小化,生成按照空格分隔的Token流

protected TokenStreamComponents createComponents( final String fieldName,
      final Reader reader) {
    return new TokenStreamComponents(new LowerCaseTokenizer(matchVersion , reader));
  }      

抽象類TokenStream

TokenStream 主要包含以下幾個方法:

1. boolean incrementToken()用于得到下一個Token。IndexWriter調用此方法推動Token流到下一個Token。實作類必須實作此方法并更新Attribute資訊到下一個Token。

2. public void reset() 重設Token流到開始,使得此TokenStrean 可以重新開始傳回各個分詞。

和原來的TokenStream傳回一個Token 對象不同,Lucene 3.0 開始,TokenStream已經不傳回Token對象了,那麼如何儲存下一個Token 的資訊呢?

在Lucene 3.0 中,TokenStream 是繼承于AttributeSource,其包含Map,儲存從class 到對象的映射,進而可以儲存不同類型的對象的值。在TokenStream 中,經常用到的對象是CharTermAttributeImpl,用來儲存Token 字元串;PositionIncrementAttributeImpl 用來儲存位置資訊;OffsetAttributeImpl 用來儲存偏移量資訊。是以當生成TokenStream 的時候, 往往調用CharTermAttribute tokenAtt = addAttribute(CharTermAttribute.class)将CharTermAttributeImpl添加到Map 中,并儲存一個成員變量。在incrementToken() 中, 将下一個Token 的資訊寫入目前的tokenAtt , 然後使用CharTermAttributeImpl.buffer()得到Token 的字元串。

注:Lucene 3.1開始廢棄了TermAttribute和TermAttributeImpl,用CharTermAttribute和CharTermAttributeImpl代替。

Token attributes

如上述,Token的資訊真正存在于各個AttributeImpl中,lucene内建的所有Attribute接口都在org.apache.lucene.analysis.tokenattributes包中。

Token attributes API的使用

1. 調用addAttribute(繼承于AttributeSource)方法,傳回一個對應屬性接口的實作類,以獲得需要的屬性。

2. 遞歸TokenStream incrementToken()方法,周遊Token流。當incrementToken傳回true時,其中Token的屬性資訊會将内部狀态修改為下個詞彙單元。

3. lucene内建Attribute接口都是可讀寫的,TokenStream 在周遊Token流時,會調用Attribute接口的set方法,修改屬性資訊。

lucene内建常用Attribute接口

1. CharTermAttribute      儲存Token對應的term文本,Lucene 3.1開始用CharTermAttribute代替TermAttribute

2. FlagsAttribute             自定義标志位

3. OffsetAttribute            startOffset是指Term的起始字元在原始文本中的位置,endOffset則表示Term文本終止字元的下一個位置。偏移量常用于搜尋結果中高亮Snippets的生成

4. PayloadAttribute          儲存有效負載

5. TypeAttribute              儲存Token類型,預設為"word",實際中可根據Term的詞性來做自定義操作

6. PositionIncrementAttribute 

儲存相對于前一個Term的位置資訊,預設值設為1,表示所有的Term都是連續的,在位置上是一個接一個的。如果位置增量大于1,則表示Term之間有空隙,可以用這個空隙來表示被删除的Term項(如停用詞)。位置增量為0,則表示該Term項與前一個Term項在相同的位置上,0增量常用來表示詞項之間是同義詞。位置增量因子會直接影響短語查詢和跨度查詢,因為這些查詢需要知道各個Term項之間的距離。

注:并不是所有的Attribute資訊都會儲存在索引中,很多Attribute資訊隻在分析過程使用,Term進索引後部分Attribute資訊即丢棄。(如TypeAttribute、FlagsAttribute在索引階段都會被丢棄)

Lucene Token流 揭秘

lucene Token流的生成,主要依賴TokenStream 的兩個子類Tokenizer和TokenFilter

Lucene的分析過程 - HackerVirus

Tokenizer類的主要作用:接收Read對象,讀取字元串進行分詞并建立Term項。

TokenFilter類使用裝飾者模式(lucene in action中作者寫的是組合模式,本人竊以為應該是裝飾者模式),封裝另一個TokenStream類,主要負責處理輸入的Token項,然後通過新增、删除或修改Attribute的方式來修改Term流。

Lucene的分析過程 - HackerVirus

如上圖,當Analyzer從它的tokenStream方法或者reusableTokenStream方法傳回tokenStream對象後,它就開始用一個Tokenizer對象建立初始Term序列,然後再連結任意數量的TokenFilter來修改這些Token流。這被稱為分析器鍊(analyzer chain)。

一個簡單的Analyzer:StopAnalyzer

protected TokenStreamComponents createComponents(String fieldName,
      Reader reader) {
    //LowerCaseTokenizer接收Reader,根據Character.isLetter(char)來進行分詞,并轉換為字元小寫
    final Tokenizer source = new LowerCaseTokenizer(matchVersion , reader);
    //隻有一個分析器鍊StopFilter,來去除停用詞
    return new TokenStreamComponents(source, new StopFilter(matchVersion ,
          source, stopwords));
  }      

StopAnalyzer測試

String text = "The quick brown fox jumped over the lazy dog";
System. out.println("Analyzing \"" + text + "\"");
Analyzer analyzer = new StopAnalyzer(Version.LUCENE_36);
String name = analyzer.getClass().getSimpleName();
System. out.println("" + name + ":");
System. out.print("" );
AnalyzerUtils. displayTokens(analyzer, text);
System. out.println("\n" );      

結果輸出

Analyzing "The quick brown fox jumped over the lazy dog"

  StopAnalyzer:

    [quick] [brown] [fox] [jumped] [over] [lazy] [dog]

posted on

2012-09-19 14:54 

HackerVirus 

閱讀(202) 

評論(0) 

編輯 

收藏 

舉報

Lucene的分析過程 - HackerVirus