1. 程式人生 > >LuceneInAction(第2版)學習筆記——第四章 Lucene的分析過程

LuceneInAction(第2版)學習筆記——第四章 Lucene的分析過程

 分析Analysis,在Lucene中指的是將域(Field)文字轉換成最基本的索引表示單元————項(term)的過程。
 在搜尋過程中,這些項用於決定什麼樣的文件能夠匹配查詢條件。

 分析器對分析操作進行了封裝,它通過執行若干操作,將文字轉換成語彙單元。
 這些操作有:
  提取單詞、去除標點符號、去掉字母上的音調符號、
  將字母轉換成小寫(也稱規範化)、去除常用詞、
  將單詞還原為詞幹形式(詞幹還原)、將單詞轉換成基本形式(詞形歸併lemmatization)。


 分析器的處理過程,也稱為【語彙單元化過程】————tokenization。
 通過【語彙單元化過程】,從文字流中提取文字塊,這些文字塊稱為 【語彙單元token】。


 
 在建立索引時,通過分析過程得到的【語彙單元token】就是被索引的項。
 最重要的是,只有被索引的項才能被搜尋到。(除非該域沒有進行分析,則整個域值都被作為一個語彙單元。)

 【語彙單元token】與它的【域名】結合合,就形成了【項term】。

 【項term】= 【域名】 + 【語彙單元token】


 使用Lucene時,選擇一個合適的分析器是非常關鍵的。
 對分析器的選擇沒有唯一標準,待分析的語種是影響選擇的因素之一。
 影響選擇的另一因素是被分析的文字所屬的領域,不同的行業有不同的術語、縮寫詞和縮略語。
 注意:不存在能適用於所有情況的分析器。

 【分析器Analyzer】 = 【分詞器Tokenizer】+【N個過濾器Filter】

通常情況下,過濾器的工作比切詞器簡單,過濾器拿著每個token,決定是繼續流轉下去或者替換或者拋棄。

鏈上的每個過濾器按順序執行,因此註明過濾器的順序顯得很重要。通常情況下,先執行普通過濾器,然後執行專業過濾器。

1. 使用分析器


1.1. 分析器說明


 1) 什麼時候要用分析器


  分析操作會出現在任何需要將文字轉換成項的時候。
  對於Lucene核心來說,分析操作會出現在兩個時間點:
   建立索引期間和使用QueryParser物件進行搜尋時。
  此外,如果搜尋結果要高亮顯示被搜尋內容,也可能要用於分析操作。
  當然,高亮顯示功能要呼叫Lucene的兩個軟體捐贈模組。

 2) 分析結果中的語彙單元取決於對應的分析器


  不同的分析器分析同樣的域值,得到的語彙單元可能是不一樣的。


 3) 四個常用分析器說明


  A. WhitespaceAnalyzer


   通過空格來分割文字資訊,不做其它規範化處理。


  B. SimpleAnalyzer


   通過非字母字元來分割文字資訊,然後將語彙單元統一為小寫形式。
   注意,它會去掉數字型別的字元,但會保留其它字元。


  C. StopAnalyzer


   與SimpleAnalyzer類似,區別在於StopAnalyzer會去掉常用單詞。
   預設會去掉常用單詞(the a等),但也可以自己設定常用單詞。


  D. StandardAnalyzer       

 
   是Lucene最複雜的核心分析器。
   它包含大量的邏輯操作來識別某些種類的語彙單元,如公司名稱、Email地址、主機名稱等。
   它還會將語彙單元轉換成小寫形式,並去除停用詞和標點符號。

 Lucene的分析結果對於搜尋使用者來說是不可見的。
 從原始文字中提取的項會被立即編入索引,並且在搜尋時用於匹配對應文件。
 當使用QueryParser進行搜尋時,Lucene會再次進行分析操作,
 這次分析的內容是使用者輸入的查詢語句的文字資訊,這樣就能確保最佳匹配效果。

1.2. 索引過程中的分析


 在索引期間,文件域值所包含的文字資訊需要被轉換成語彙單元。

 程式首先要例項化一個Analyzer物件,然後將之傳遞給IndexWriter物件。
  Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
  IndexWriter writer = new IndexWriter(dir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED);

 如果某個域不被分析,則該域值被整個索引成一個單獨的語彙單元。
 在用IndexWriter例項索引文件時,一般通過預設的分析器來分析文件中的每個域。
 如某個文件需要用特殊分析器處理的話,可以針對這個文件指定分析器:
  IndexWriter的addDocument()和updateDocument()都允許為某個文件選擇對應的分析器。

 Field.Index.ANALYZED和Field.Index.ANALYXED_NO_NORMS兩個引數,保證該域會被分析。
 Field.NOT_Index.ANALYZED和Field.Index.NOT_ANALYXED_NO_NORMS兩個引數,保證該域不會被分析,
  而是將該域值作為一個語彙單元處理。


 
1.3. QueryParser分析


 QueryParser能很好地為搜尋使用者提供形式自由的查詢。
 為完成這個任務,QueryParser使用分析器將文字資訊分割成各個項用於搜尋。

 在例項化QueryParser物件時,同樣需要傳入一個分析器物件:
  QueryParser parser = new QueryParser(Version.LUCENE_30, "contents", analyzer);
  Query query = parser.parse(expression);
 
 分析器會接收表示式中連續的獨立的文字片段,但不會接收整個表示式。
 這個表示式可能包含操作符、圓括號或其它表示範圍、萬用字元以及模糊查詢在內的特殊表示式語法。

 查詢時,QueryParser所使用的分析器必須和索引期間使用的分析器相同嗎?
 不一定。

1.4. 解析 VS 分析 : 分析器何時不再適用


 在Lucene內部,分析器用於將域的文字內容單元化,這是分析器的一個重點任務。
 分析器可以用於每次分析一個特定的域,並且將該域內容分解為語彙單元,但是在分析器內不可以建立新的域。

 分析器不能用於從文件中分離和提取域,因為分析器的職責範圍只是每次處理一個域。
 為了對域進行分離,要在分析之前預先解析這些文件。

 【解析】: 從文件中分離和提取域,可能會產生多個域
 【分析】: 每次分析一個特定的域,並且將該域內容分解為語彙單元,在分析器內不可以建立新的域

2. 剖析分析器


2.1. 分析器的工作原理


 (1) Analyzer類


  Analyzer類是一個抽象類,是所有分析器的基類。
  它通過TokenStream類,以一種很好的方式將文字逐字轉換為語彙單元流。

  分析器實現TokenStream物件的唯一宣告方法是:
  public TokenStream tokenStream(String fieldName, Reader reader)
  返回的TokenStream物件,就用來遞迴處理所有的語彙單元。

 (2) SimpleAnalyzer類


  SimpleAnalyzer分析器是如何工作的?

  public final class SimpleAnalyzer extends Analyzer{

  public TokenStream tokenStream(String fieldName, Reader reader){
   return new LowerCaseTokenizer(reader);
   //LowerCaseTokenizer物件依據文字中的非字母字元來分割文字,去掉非字母字元,
   //並且,將字母轉換成小寫形式
   //非字母字元通過 Character.isLetter()方法來判斷
  }

  public TokenStream reusableTokenStream(String fieldName, Reader reader)
   throws IOException {

   Tokenizer tokenizer = (Tokenizer) getPreviousTokenStream();

   if(tokenizer == null){
    tokenizer = new LowerCaseTokenizer(reader);
    setPreviousTokenStream(tokenizer);
   }else{
    tokenizer.reset(reader);
   }

   return tokenizer;
  }
  //reusableTokenStream()方法是個可選方法,分析器可以實現這個方法,並通過它得到更好地索引效率
  //該方法,可重複利用之前向對應執行緒返回的同一個TokenStream物件。
  //這種方式,可以節約大量的磁碟分配空間和垃圾蒐集開銷
  //如果不這樣處理,則每個文件的每個域都要重新初始化一個TokenStream物件


  }

  (3) Analyzer基類的工具類方法


  Analyzer基類實現了兩個工具類方法,即setPreviousTokenStream()和getPreviousTokenStream()。
  它們為本機儲存執行緒提供了針對TokenStream物件的儲存和回收功能。 

  Lucene的所有內建分析器都要實現如下方法:
  該方法首次被某執行緒呼叫時,必須建立並存儲一個新的TokenStream物件。
  在將該分析器指派給新的Reader物件後,後續呼叫該分析器時就會返回先前建立的TokenStream物件。

2.2. 語彙單元的組成


 (1) 語彙單元內容


 【語彙單元流TokenStream】是分析過程所產生的基本單元。
 在索引時,Lucene使用特定的分析器來處理需要被語彙單元化的域,
 而每個語彙單元相關的重要屬性隨即被編入索引中。

 每個語彙單元表示一個獨立的單詞。
 一個語彙單元攜帶了一個文字值(單詞本身)和其它一些元資料,如:
  原始文字從起點到終點的偏移量、語彙單元的型別、以及位置增量。
 語彙單元可以選擇性包括一些由程式定義的標誌位和任意位元組數的有效負載,
 這樣程式就能根據具體需要來處理這些語彙單元。

 起點偏移量是指語彙單元文字的起始字元在原始文字中的位置,
 終點偏移量則表示語彙單元文字終止字元的下一個位置。

 偏移量對於在搜尋結果中用高亮顯示匹配的語彙單元非常有用。

 語彙單元的型別是用String物件表示的,預設值是"word"。
 如果需要,可以在語彙單元的過濾過程中控制和利用到其型別屬性。

 文字被語彙單元化之後,相對於前一個語彙單元的位置資訊,是以位置增量值儲存的。
 大多數內建的語彙單元化模組都將位置增量的預設值設為1,
 表示所有語彙單元都是連續的,在位置上是一個接一個的。

 每個語彙單元還有多個選擇標誌,一個標誌即32bit。
 Lucene的內建分析器不能使用這些標誌,但自己設計的搜尋程式是可以使用它們的。

 此外,每個語彙單元都能以byte[]形式記錄在索引中,用以指向有效負載。

 (2) 語彙單元轉換成項


 當文字在索引過程中進行分析後,每個語彙單元都作為一個項被傳遞給索引。
 位置增量、起點和終點偏移量和有效負載是語彙單元攜帶到索引中的唯一附加元資料。
 而語彙單元的型別和標誌位都被拋棄了————它們只在分析過程中使用。

 (3) 位置增量


 位置增量使得當前語彙單元和前一個語彙單元在位置上關聯起來。
 一般來說位置增量為1,表示每個單詞存在於域中唯一且連續的位置上。
 位置增量因子會直接影響短語查詢和跨度查詢,因為這些查詢需要知道域中各個項之間的距離。

 如果位置增量大於1,則允許語彙單元之間有空隙,可以用這個空隙來表示被刪除的單詞。
 位置增量為0的語彙單元表示將該語彙單元放置在前一個語彙單元的位置上。

 同義詞分析器可以通過0增量來表示插入的同義詞。
 這個做法使得Lucene在進行短語查詢時,輸入任意一個同義詞都能匹配到同一結果。

2.3. 語彙單元流揭祕


2.3.1. 語彙單元流組成


 TokenStream是一個能在被呼叫後產生語彙單元序列的類。
 它有兩種不同的型別:
  Tokenizer類 : 通過java.io.Reader物件讀取字元並建立語彙單元
  TokenFilter類 : 封裝了另外一個TokenStream抽象類的組合模式(該抽象可以是另外一個TokenFilter類)
      TokenFilter負責處理輸入的語彙單元,然後通過增、刪、改屬性的方式來產生新的語彙單元

 這兩個類都是從TokenStream繼承而來。

 當分析器從它的tokenStream()方法或reusableTokenStream()方法返回tokenStream物件後,
 它就開始用一個tokenizer物件建立初始語彙單元序列,然後再連結任意數量的tokenFilter物件來修改這些語彙單元。
 這些稱為分析器鏈(analyzer chain)。 如:
  Reader --> Tokenizer --> TokenFilter --> TokenFilter --> TokenFilter --> Tokens

 分析器鏈以一個Tokenizer物件開始,通過Reader物件讀取字元併產生初始語彙單元,
 然後用任意數量的連結TokenFilter物件修改這些語彙單元。

 【語彙單元流TokenStream】 = 【分詞器Tokenizer】 + N個【語彙過濾器TokenFilter】
 分析器Tokenizer類和語彙過濾器TokenFilter類,兩個類都是從TokenStream繼承而來

2.3.2. Lucene的核心Tokenizer和TokenFilter


 1) TokenStream : 抽象Tokenizer基類


 2) Tokenizer : 輸入引數為Reader物件的TokenStream子類


 3) 【CharTokenizer】: 基於字元的Tokenizer父類,包含抽象方法isTokenChar()


    當isTokenChar()為true時,輸出連續的語彙單元塊
    它還能將字元規範化處理,如轉為小寫形式
    輸出的語彙單元所包含的最大字元數為255


 4) WhitespaceTokenizer :


  isTokenizer()為true時的CharTokenizer類,用於處理所有非空格字元


 5) 【KeywordTokenizer】 : 將輸入的整個字串轉換為一個語彙單元


 6) LetterTokenizer  : isTokenChar()為true,且Charcter.isLetter為true時的CharTokenizer類


 7) LowerCaseTokenizer : 將所有字元規範化處理為小寫形式的LetterTokenizer類


 8) SinkTokenizer : Tokenizer子類,用於吸收語彙單元


    能將語彙單元快取至一個私有列表中,並能遞迴訪問該列表中的語彙單元.
    該類與TeeTokenizer聯合使用時,用於"拆分"TokenStream物件。


 9) 【StandardTokenizer】: 複雜而基於語法的語彙單元產生器,用於輸出高級別型別的語彙單元。


    如:Email地址。
    每個輸出的語彙單元標記為一個特殊型別,
    這些型別中的一部分需要用StandardTokenizer特殊處理。

 
 10) TokenFilter : 輸入引數為另一個TokenStream子類的TokenStream子類。 


 11) LowerCaseFilter : 將語彙單元轉成小寫形式


 12) StopFilter : 移除指定集合中的停用詞


 13) PorterStemFilter : 利用Poster詞幹提取演算法將語彙單元還原為詞幹


 14) TeeTokenFilter : 將語彙單元寫入SinkTokenizer物件,完成對TokenStream物件的拆分


    該類還會返回未被修改的語彙單元


 15) ASCIIFoldingFilter: 將帶音調的字元對映為不帶音調的字元


 16) CachingTokenFilter: 儲存從輸入字元流中提取的所有語彙單元,呼叫該類的reset()方法後能重複以上處理


 17) LengthFilter : 支援特定長度的語彙單元


 18) StandardFilter :  接收一個StandardTokenizer物件作為引數


    用於去掉縮略詞中的點號,或者在帶有單引號的單詞中去掉's


2.3.3. 分析器鏈示例


 public TokenStream tokenStream(String fieldName, Reader reader) {
  return new StopFilter(true, new LowerCaseTokenizer(reader), stopWords);
 }

 在該分析器中,LowerCaseTokenizer物件會通過Reader物件輸出原始語彙單元集,
 然後將這個語彙單元集傳遞給StopFilter物件的初始化方未能。

 LowerCaseTokenizer物件所輸出的語彙單元對應於原始文字中的鄰接字母,同時還會將這些字母轉為小寫形式。
 語彙單元的邊界以非字母字元來區分,同時,這些非字母字元不會出現在語彙單元中。
 
 經過語彙單元轉換為小寫形式後,StopFilter會呼叫positionIncrement()方法,
 該方法查詢停用詞列表,移除語彙單元中的停用詞。
 

2.3.4. 語彙單元流其它功能


 在實現TokenStream抽象類時,加入快取功能是必要的。
 底層的Tokenizer物件使用快取來儲存字元,以便在遇到空格和非字母等邊界時生成語彙單元。
 TokenFilter物件在向資料流中輸出附加的語彙單元時,需要將當前語彙單元和附加語彙單元進行排列,
 並且,一次只能輸出一個語彙單元。

 大多數Lucene內建的TokenFilter類都對輸入的語彙單元流進行了某種程度的修改,
 而其中一個SeeSinkTokenFilter類更是如此。
 任意該過濾器會將輸入的語彙單元流複製成任意數量的拷貝傳送到sink輸出流和標準輸出流中,
 每個sink輸出流都可以用於進一步處理。
 該功能在一些情況下很好用: 當兩個或多個域共享同一個初始化分析步驟,
 但相互之間在語彙單元的最終處理過程中又存在差異。

2.4. 觀察分析器


 通常情況下,分析過程所產生的語彙單元會在毫無提示的情況下用於索引操作。
 然後,如能跟蹤到語彙單元的生成,則能對分析過程有一個具體的瞭解。


2.4.1. AnalyzerDemo : 分析操作例項


 
 public class AnalyzerDemo {
  private static final String[] examples = {
   "The quick brown fox jumped over the lazy dog",
   "XY&Z Corporation - [email protected]"
  };

  private static final Analyzer[] analyzers = new Analyzer[] {
   new WhitespaceAnalyzer(),
   new SimpleAnalyzer(),
   new StopAnalyzer(Version.LUCENE_30),
   new StandardAnalyzer(Version.LUCENE_30)
  };

  public static void main(String[] args) throws IOException {
   String[] strings = examples;
   if(args.length >0){
    strings=args;//分析處理的命令列引數
   }

  }

  private static void analyze(String text) throws IOException {
   System.out.println("Analyzing \"" + text + "\"");
   for(Analyzer analyzer : analyzers) {
    String name = analyzer.getClass().getSimpleName();
    System.out.println(" " + name + ":");
    System.out.println("    ");
    AnalyzerUtils.displayTokens(analyzer, text); //執行實際操作
    //AnalyzerUtils中,分析器用於處理提取出來的文字和語彙單元
    System.out.println("\n");
   }
  }
 }

2.4.2. AnalyzerUtils : 深入研究分析器


 
 public static void displayTokens(Analyzer analyzer,
     String text) throws IOException {

  displayTokens(analyzer.tokenStream("contents", new StringReader(text)));//呼叫分析過程

 }

 public static void displayTokens(TokenStream stream) throws IOException {
  TermAttribute term = stream.addAttribute(TermAttribute.class);
  white(stream.incrementToken()){
   System.out.println("[" + term.term() + "]");   //輸出帶括號的語彙單元
  }
 }

2.4.3. 深入分析語彙單元


 TokenFilter物件會訪問和修改輸入的語彙單元流,但語彙單元流是由哪些屬性組成的呢?
 為更好的說明,我為在AnalyzerUtils類中加了一個名為displayTokensWithFullDetails()的方法。
 該方法,可以觀察各個語彙單元的項、偏移量、型別和位置增量。

 public static void displayTokensWithFullDetails(Analyzer analyzer, String text) throws IOException{
  
  TokenStream stream = analyzer.tokenStream("contents", new StringReader(text));//執行分析

  //獲取有用屬性
  TermAttribute term = stream.addAttribute(TermAttribute.class);
  PositionIncrementAttribute posIncr = stream.addAttribute(PositionIncrementAttribute.class);
  OffsetAttribute offset = stream.addAttribute(OffsetAttribute.class);
  TypeAttribute type = stream.addAttribute(TypeAttribute.class);

  //遞迴處理所有語彙單元
  int position = 0;
  while(stream.incrementToken()){
   //計算位置資訊並列印
   int increment = posIncr.getPositionIncrement();
   if(increment>0){
    position = position + increment;
    System.out.println();
    System.out.print(position + ":");
   }

   //列印所有語彙單元細節資訊
   System.out.print("[" +
     term.term() + ":" +
     offset.startOffset() + "->" +
     offset.endOffset() + ":" +
     type.type() + "]");


  }
  System.out.println();

 }

           
2.4.4. 語彙單元的屬性


 注意,TokenStream永遠不會顯式建立包含所有語彙單元屬性的單個物件。
 它會分別與語彙單元每個元素(項、偏移量、位置增量)對應的可重用屬性介面進行互動。


TokenStream繼承類AttributeSource並生成子類,AttributeSource是一個實用方法。
它能在程式非執行期間提供增強型別並且能夠完全擴充套件的屬性操作,這給我們帶來了很好的執行效能。
Lucene在分析期間使用某些預定義的屬性,但還是可以自由加入自己的屬性,方法是建立實現Attribute介面的具體類。
注意:Lucene在索引期間不會對這些新屬性作任何操作。


Lucene內建的語彙單元屬性:
TermAttribute 語彙單元對應的文字
PositionIncrementAttribute 位置增量,預設值為1
OffsetAttribute 起始字元和終止字元的偏移量
TypeAttribute 語彙單元型別,預設為單詞
FlagsAttribute 自定義標誌位
PayloadAttribute 每個語彙單元的byte[]型別有效負載

2.4.5. 起始和結束位置偏移量有什麼好處?

它們記錄的是初始字元在語彙單元中的偏移量,它們並不屬於Lucene的核心功能。

並且,它們對於各個語彙單元來說,只是不透明的整數,你可以在這裡設定任意的整數。

2.4.6. 語彙單元型別的作用

你可以通過語彙單元的型別值將語彙單元指明為某種特定型別 的詞彙。

StandardAnalyzer是唯一一個能影響語彙單元型別的內建分析器。

預設情況下,Lucene並不會將語彙單元的型別編入索引,因此,該型別只會在分析時使用。

2.5. 語彙單元過濾器:過濾順序的重要性

對於某些TokenFilter子類來說,在分析過程中對事件的處理順序是非常重要的。
每個步驟也許都必須一覽前一個步驟才能完成。
移除停用詞的處理就是一個很好的例子。
StopFilter類在停用詞集合中區分大小寫地查對每一個語彙單元,這個步驟就依賴於輸入小寫形式的語彙單元。


到目前為止,你應該對分析過程的內部機制有了一個堅實的掌握。
分析器會簡單地定義一個語彙單元鏈,該鏈的開端 是新語彙單元tokenStream的初始資料來源。

其後跟隨任意數量的用於修改語彙單元的tokenFilter子類。


語彙單元包含一個屬性集,Lucene會通過各種不同的方法來儲存該集合。

3. 使用內建分析器

3.1. 常見內建分析器的功能


Lucene包含了一些內建分析器,它們由某些內建的語彙單元 Tokenizer 和 TokenFilter 聯合組成。


WhitespaceAnalyzer 根據空格拆分語彙單元
SimpleAnalyzer       根據非字母字元拆分文字,並將其轉換為小寫形式
StopAnalyzer           根據非字母字元拆分文字,然後小寫化,再移除停用詞
KeywordAnalyzer    將整個文字作為一個單一語彙單元處理
StandardAnalyzer   基於複雜的語法來生成語彙單元,該語法能識別email地址、首字母縮寫詞、
           漢語、日語、韓語字元、字母數字等。還能完成小寫轉換和移除停用詞。

這些內建分析器,幾乎可以用於分析所有的西方(主要是歐洲)的語言。

3.2. StopAnalyzer

StopAnalyzer分析器除了完成基本的單詞拆分和小寫化功能之外,還負責移除一些稱之為停用詞(stop words)的特殊單詞。


停用詞是指較為通用的詞,如the,它對於搜尋來說,詞義單一,幾乎每個文件都會包含這樣的詞。
StopAnalyzer類內建了一個常用英文停用詞集合,該集合為ENGLISH_STOP_WORDS_SET。
StopAnalyzer還有一個可過載的構造方法,允許你通過它傳入自己的停用詞集合。
初始化StopAnalyzer時,系統會在後臺建立一個StopFilter物件以完成過濾功能。

3.3. StandardAnalyzer

StandardAnalyzer是公認的最實用的Lucene內建分析器。


該分析器基於JFlex-based語法操作,能夠將相關型別的詞彙準確地轉化為語彙單元。


StandardAnalyzer還能通過以StopAnalyzer相同的機制移除停用詞。
它們使用同一個預設的英文停用詞集合,也可以通過過載方法選擇自定義的停用詞集合。
因此,StandardAnalyzer是你不二的選擇。

3.4. 應當採用哪種核心分析器

大多數應用程式都不使用任意一種內建分析器,而是選擇建立自己的分析器鏈。


對於使用核心分析器的應用程式來說,StandardAnalyzer可能是最常用的選擇。
某個包含部分數字列表的域可能會使用 WhitespaceAnalyzer 。

事實上,Solr能夠輕鬆建立自定義的分析鏈,方法是在solrconfig.xml檔案中,以XML格式直接定義分析連結串列達式。


4. 近音詞查詢

近音詞演算法: Metaphone或Soundex
預設情況下的語彙單元型別是word,而近音詞過濾器中,使用metaphone作為語彙單元型別。
只有在特殊情況下,才會以發音相似作為匹配條件進行搜尋。

          
5. 同義詞、別名和其它表示相同意義的詞

同義詞分析器,主要是位置增量為0的同義詞。同義詞是雙向的。
只有在索引期間或搜尋期間才需要擴充套件同義詞,如在兩個處理階段內都進行同義詞擴充套件則是對系統資源的浪費。

如果在索引期間進行同義詞的擴充套件,則索引所佔用的磁碟空間會稍大一些,但這能加快搜索速度。
因為這樣使得被訪問的搜尋項更少。
如果同義詞被編入索引,則不能隨意快速地更改它們,也不能在搜尋期間隨意觀察這些改變所帶來的效果。

如果在搜尋期間進行同義詞的擴充套件,則能在測試時很快觀察到這種改變帶來的效果。


6. 詞幹分析

 詞幹演算法,也稱詞幹提取器,是對英文單詞中較常見的,因時態、語態、複數格等原因引起的詞尾變化進行移除的處理過程。
即:經過詞幹演算法的處理,每個單詞的各種形式都會被還原為其詞幹形式。
詞幹演算法有 Porter演算法、Snowball演算法等。


7. 域分析

7.1. 多值域分析


多值域:一個域有多個值,儲存時,每個值都存放在相同名稱的域例項中。

文件可能包含同名的多個Field例項,而Lucene在索引過程中,在邏輯上將這些域的語彙單元按照順序附加在一起。
預設情況下,getPositionIncrementGap()方法返回0,表示無間隙,則它在執行時,會認為各個域值是連線在一起的。
如果該值加大,則位置查詢就不會錯誤地在各個域值邊界進行匹配了。
還有一點很重要,那就是,要確保正常計算多值域的語彙單元偏移量。

即:如果程式不需要對各個域值連線處的文字進行匹配操作,則可以設定位置增量。

7.2. 特定域分析


在索引操作期間,對於分析器選擇的粒度為 IndexWriter 或文件級別。
如果使用QueryParser,則程式會只用一個分析器來處理文字。
但有時,不同的域要使用不同的分析器來分析。

在程式內部,分析器能輕易地處理正在被處理的域名,因為這個域名是作為引數傳入其tokenStream方法的。
Lucene內建的分析器卻不能擴充套件這種能力,因為它被設計用來處理一般情況,而域名則是與應用程式有關的。

不過,可以很容易建立一個自定義分析器來處理這種情況。
作為可選,Lucene的內建工具類 PerFieldAnalyzerWapper,這個類,可以針對每個域使用不同的分析器。


//在建立 PerFieldAnalyzerWapper 時,要提供預設的分析器。
PerFieldAnalyzerWapper analyzer = new PerFieldAnalyzerWapper(new SimpleAnalyzer());
//要使用不同的分析器處理域時,可以呼叫其addAnalyzer()方法
//其它沒有指定的域,則使用預設的分析器
analyzer.addAnalyzer("body", new StandardAnalyzer(Version.LUCENE_30));


7.3. 搜尋未被分析的域


有多種解決方案,其中最簡單的方案就是使用 PerFieldAnalyzerWapper 。


8. 語言分析

8.1. 分析不同語言的文字時要處理的問題

在分析不同語言的文字時,必須面臨以下幾個問題:
首先要正確設定字元的編碼方式,從而使Java能夠順利地讀取諸如檔案等外部資料。
在分析過程中,每種語言都有其不同的停用詞集合,以及特有的詞幹分析演算法
而且,根據每種語言的不同需要,可能還要把一些聲調符號從字元中去掉
最後,如果事先不知道是何種語言,則程式還要對此判斷。

至於,如何利用Lucene提供的基本模組處理上述問題,最終要由開發者決定。
此外,在Lucene的contrib目錄和網際網路上,還有諸如Tokenizer和TokenStream等大量的分析器和附加模組可用。

8.2. Unicode與字元編碼


在Lucene內部,所有的字元都是以標準的UTF-8編碼儲存的。
Java會在字串物件內對Unicode編碼進行自動處理,用UTF-16格式表示字元。
但,外部文字傳遞給Java和Lucene時的編碼處理,Lucene是不負責的,而必須由自己負責。


8.3. 非英語語種分析


在處理非英文文字時,原來分析過程中用到的各種細節仍然適用。
分析文字的目的就是從中提取出所有項。
對於西方語言來說,分割單詞使用的是空格和標點。
所以,必須把西方語言中的停用詞列表以及詞幹還原演算法,調整為針對特定語種的文字,以便對之進行分析。

8.4. 字元規範化處理


Lucene提供了一套字元過濾類,用來表示基於語彙單元的相關功能。
Lucene提供的單一的CharFilter核心子類,稱為MappingCharFilter,該類允許你接收輸入和輸出字串。

Lucene的核心分析器都沒有字元過濾功能,必須建立自己的分析器。
該分析器以一個CharReader作為開端,後跟任意數量的CharFilter,之後,再建立Tokenizer和TokenFilter鏈。


8.5. 亞洲語種分析


對於漢日韓(CJK)等亞洲語種來說,一般使用表意文字而不是使用由字母組成的單詞。
這些象形字元不一定通過空格來分隔,所以需要使用一種完全不同的分析方法來識別和分隔語彙單元。

StandardAnalyzer是Lucene內建的唯一能夠處理亞洲語種的分析器。
該分析器可以將一定範圍內的Unicode編碼識別為CJK字元,並將它們拆分為獨立的語彙單元。

Lucene的contrib目錄中有3個分析器適合處理亞洲語種:CJKAnalyzer、ChineseAnalyzer和Smart ChineseAnalyzer。


8.6. 有關非英語語種分析的其它問題


當在同一索引中處理各種不同語種時,所碰到的主要障礙是:如何處理文字編碼
這時,StandardAnalyzer仍然是Lucene中最好的通用內建分析器,它甚至還可以用於處理CJK字元;
不過,捐贈的SmartChineseAnalyzer類似乎更適用於對中文進行分析。

當希望把由多種語言構成的文件編入同一索引時,最好為每個文件都指定相應的分析器。

最後要介紹的是語種檢測,和字元編碼一樣,它也超出了Lucene所涉及的範圍。
但它對搜尋程式來說很重要,而它也是當前搜尋技術中較為活躍的研究領域之一。


9. Nutch分析

我們不可能得到Google的原始碼,但可以得到與之相似的開源專案Nutch的原始碼。
它是由Lucene的開發者Doug Cutting開發的。

Nutch對文字的分析方式很有趣,它對停用詞做了一些特殊處理,在Nutch中將停用詞稱為常用項common terms。
如果在查詢中使用了常用項,並且該項不是包含在帶有其它修飾詞或引號的某個短語中,則程式將忽略這些常用詞。

Nutch把【索引期間】進行分析所使用的【二元語法技術】與【查詢期間】【對短語的優化技術】結合在一起。
這種結合可以大大減小搜尋時需要考慮的文件範圍。