天天看點

Lucene分詞器詳解

學習目标:

  • 了解Lucene中提供的分詞器實作
  • 熟練使用Lucene分詞器API
  • 掌握IKAnaylzer中文分詞器的內建

Lucene-分詞器API

1、org.apache.lucene.analysi.Analyzer

  • 分析器,分詞器元件的核心API,它的職責:建構真正對文本進行分詞處理的TokenStream(分詞處理器)。通過調用它的如下兩個方法,得到輸入文本的分詞處理器。
public final TokenStream tokenStream(String fieldName, Reader reader)
public final TokenStream tokenStream(String fieldName, String text)      

這兩個方法是final方法,不能被覆寫的,在這兩個方法中是如何建構分詞處理器的呢?

請檢視源碼,回答後面的問題

  • 問題1:從哪裡得到了TokenStream?
  • 問題2:方法傳入的字元流Reader 給了誰?
  • 問題3:components是什麼?components的擷取邏輯是怎樣?
  • 問題4:createComponents(fieldName) 方法是個什麼方法?
  • 問題5:Analyzer能直接建立對象嗎?
  • 問題6:為什麼它要這樣設計?
  • 問題7:請看一下Analyzer的實作子類有哪些?
  • 問題8:要實作一個自己的Analyzer,必須實作哪個方法?

2、TokenStreamComponents createComponents(String fieldName)

  • 是Analizer中唯一的抽象方法,擴充點。通過提供該方法的實作來實作自己的Analyzer。
  • 參數說明:fieldName,如果我們需要為不同的字段建立不同的分詞處理器元件,則可根據這個參數來判斷。否則,就用不到這個參數。
  • 傳回值為 TokenStreamComponents 分詞處理器元件。
  • 我們需要在createComponents方法中建立我們想要的分詞處理器元件。

請看它的源碼,回答後面的問題

  TokenStreamComponents 是什麼?

3、TokenStreamComponents

  • 分詞處理器元件:這個類中封裝有供外部使用的TokenStream分詞處理器。提供了對source(源)和sink(供外部使用分詞處理器)兩個屬性的通路方法。

請看它的源碼,回答後面的問題

  • 問題1:這個類的構造方法有幾個?差別是什麼?從中能發現什麼?
  • 問題2:source 和 sink屬性分别是什麼類型?這兩個類型有什麼關系?
  • 問題3:在這個類中沒有建立source、sink對象的代碼(而是由構造方法傳人)。也就是說我們在Analyzer.createComponents方法中建立它的對象前,需先建立什麼?
  • 問題4:在Analyzer中tokenStream() 方法中把輸入流給了誰?得到的TokenStream對象是誰?TokenStream對象sink中是否必須封裝有source對象? 如果必須有,這個封裝是否也得在Analyzer.createComponents方法中完成?

4、org.apache.lucene.analysis.TokenStream

  • 分詞處理器,負責對輸入文本完成分詞、處理。

請看它的源碼,回答後面的問題

  • 問題1:TokenStream中有沒有對應的給入方法?
  • 問題2:TokenStream是一個抽象類,有哪些方法,它的抽象方法有哪些?它的構造方法有什麼特點?
  • 問題3:TokenStream的具體子類分為哪兩類?有什麼差別?概念說明:Token: 分項,從字元流中分出一個一個的項
  • 問題4:TokenStream繼承了誰?它是幹什麼用的?概念說明:Token Attribute: 分項屬性(分項的資訊):如 包含的詞、位置等

5、TokenStream 的兩類子類

  • Tokenizer:分詞器,輸入是Reader字元流的TokenStream,完成從流中分出分項
  • TokenFilter:分項過濾器,它的輸入是另一個TokenStream,完成對從上一個TokenStream中流出的token的特殊處理。

請看它的源碼,回答後面的問題

  • 問題1:請檢視Tokenizer類的源碼及注釋,這個類該如何使用?要實作自己的Tokenizer隻需要做什麼?
  • 問題2:Tokenizer的子類有哪些?
  • 問題3:請檢視TokenFilter類的源碼及注釋,如何實作自己的TokenFilter?
  • 問題4:TokenFilter的子類有哪些?
  • 問題5:TokenFilter是不是一個典型的裝飾器模式?如果我們需要對分詞進行各種處理,隻需要按我們的處理順序一層層包裹即可(每一層完成特定的處理)。不同的處理需要,隻需不同的包裹順序、層數。

6、TokenStream 繼承了 AttributeSource

  • 問題1:我們在TokenStream及它的兩個子類中是否有看到關于分項資訊的存儲,如該分項的詞是什麼、這個詞的位置索引?概念說明:Attribute 屬性 Token Attribute 分項屬性(分項資訊),如 分項的詞、詞的索引位置等等。這些屬性通過不同的Tokenizer /TokenFilter處理統計得出。不同的Tokenizer/TokenFilter組合,就會有不同的分項資訊。它是會動态變化的,你不知道有多少,是什麼。那該如何實作分項資訊的存儲呢?

答案就是 AttributeSource、Attribute 、AttributeImpl、AttributeFactory

  • AttribureSource 負責存放Attribute對象,它提供對應的存、取方法
  • Attribute對象中則可以存儲一個或多個屬性資訊
  • AttributeFactory 則是負責建立Attributre對象的工廠,在TokenStream中預設使用了AttributeFactory.getStaticImplementation 我們不需要提供,遵守它的規則即可。

7、AttributeSource使用規則說明

  1、某個TokenStream實作中如要存儲分項屬性,通過AttributeSource的兩個add方法之一,往AttributeSource中加入屬性對象。

    <T extends Attribute> T addAttribute(Class<T> attClass)

    該方法要求傳人你需要添加的屬性的接口類(繼承Attribute),傳回對應的實作類執行個體給你。從接口到執行個體,這就是為什麼需要AttributeFactory的原因。

void addAttributeImpl(AttributeImpl att)

  2、加入的每一個Attribute實作類在AttributeSource中隻會有一個執行個體,分詞過程中,分項是重複使用這一執行個體來存放分項的屬性資訊。重複調用add方法添加它傳回已存儲的執行個體對象。

  3、要擷取分項的某屬性資訊,則需持有某屬性的執行個體對象,通過addAttribute方法或getAttribure方法獲得Attribute對象,再調用執行個體的方法來擷取、設定值

  4、在TokenStream中,我們用自己實作的Attribute,預設的工廠。當我們調用這個add方法時,它怎麼知道實作類是哪個?這裡有一定規則要遵守:

    •   自定義的屬性接口 MyAttribute 繼承 Attribute
    •   自定義的屬性實作類必須繼承 Attribute,實作自定義的接口MyAttribute
    •   自定義的屬性實作類必須提供無參構造方法
    •   為了讓預設工廠能根據自定義接口找到實作類,實作類名需為 接口名+Impl 。

請檢視lucene中提供的Attribute實作是否是這樣的。

8、TokenStream 的使用步驟。

  • 我們在應用中并不直接使用分詞器,隻需為索引引擎和搜尋引擎建立我們想要的分詞器對象。但我們在選擇分詞器時,會需要測試分詞器的效果,就需要知道如何使用得到的分詞處理器TokenStream,使用步驟:
    • 1、從tokenStream獲得你想要獲得分項屬性對象(資訊是存放在屬性對象中的)  
    • 2、調用 tokenStream 的 reset() 方法,進行重置。因為tokenStream是重複利用的。
    • 3、循環調用tokenStream的incrementToken(),一個一個分詞,直到它傳回false
    • 4、在循環中取出每個分項你想要的屬性值。
    • 5、調用tokenStream的end(),執行任務需要的結束處理。
    • 6、調用tokenStream的close()方法,釋放占有的資源。

思考:tokenStream是裝飾器模式,這個reset / incrementToken / end / close是如何工作的? 請檢視 tokenStream / Tokenizer / TokenFilter的源碼。

9、簡單實作一個我們自己的Analyzer,需求說明

  • Tokenizer:
    •  實作對英文按空白字元進行分詞。 
    •  需要記錄的屬性資訊有: 詞
  • TokenFilter:
    •  要進行的處理:轉為小寫

說明:Tokenizer分詞時,是從字元流中一個一個字元讀取,判斷是否是空白字元來進行分詞。

思考:Tokenizer是一個 AttributeSource對象,TokenFilter 又是一個AttributeSource對象。在這兩個我們自己的實作類中,我們都調用了addAttribute方法,怎麼會隻有一個 attribute對象? 請檢視源碼找到答案。

小結:

  • 通過API及源碼的學習,你是否感受到了作者的一些設計思想
  • 他是如何處理變與不變的,如Analyzer、TokenStream類的設計?
  • 他是如何處理不同分詞器有不同的處理邏輯的問題的?

Lucene提供的分詞器

  • Lucene core子產品中的 StandardAnalyzer 英文分詞器
    • 看看它都分析存儲哪些屬性資訊  
    • 試試它的英文分詞效果
    • 中文分詞效果
  • Lucene 的中文分詞器 SmartChineseAnalyzer
    • 試試它的中英文分詞效果
<!-- Lucene提供的中文分詞器子產品,lucene-analyzers-smartcn -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-smartcn</artifactId>
    <version>7.3.0</version>
</dependency>      
  • Lucene-Analyzers-common包中提供的分詞器
    • 看看都有些什麼分詞器

IKAnalyzer內建

1、IKAnalyzer 開源、輕量級的中文分詞器,應用比較多

  • 最先是作為lucene上使用而開發,後來發展為獨立的分詞元件。隻提供到Lucene 4.0版本的支援。我們在4.0以後版本Lucene中使用就需要簡單內建一下。
<!-- ikanalyzer 中文分詞器 -->
<dependency>
    <groupId>com.janeluo</groupId>
    <artifactId>ikanalyzer</artifactId>
    <version>2012_u6</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- lucene-queryparser 查詢分析器子產品 -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-queryparser</artifactId>
    <version>7.3.0</version>
</dependency>          

2、需要做內建,是因為Analyzer的createComponents方法API改變了。

  • 內建步驟:
    • 找到 IkAnalyzer包體提供的Lucene支援類,比較IKAnalyzer的createComponets方法。
    • 照這兩個類,建立新版本的,類裡面的代碼直接複制,修改參數即可。
Lucene分詞器詳解

3、IKAnalyzer提供兩種分詞模式:細粒度分詞和智能分詞,看它的構造參數。

4、內建後,請測試細粒度分詞、智能分詞的效果

5、請比較IKAnalyzer和SmartChineseAnalyzer

6、擴充 IKAnalyzer的停用詞

  • Ik中預設的停用詞很少,我們往往需要擴充它。可從網址: https://github.com/cseryp/stopwords 下載下傳一份比較全的停用詞。
  • Ik中停用詞的擴充步驟:
  1. 在類目錄下建立IK的配置檔案:src/main/resouce/IKAnalyzer.cfg.xml
  2. 在配置檔案中增加配置擴充停用詞檔案的節點(如有多個,以“;”間隔):<entry key=“ext_stopwords”>my_ext_stopword.dic</entry>
  3. 在類目錄下建立我們的擴充停用詞檔案 my_ext_stopword.dic
  4. 編輯該檔案加入停用詞,一行一個(注意檔案要是UTF-8編碼)
Lucene分詞器詳解

7、擴充 IKAnalyzer的詞典

  • 每年都有很多的新詞産生,往分詞器的詞典中添加新詞的步驟:
  1. 在類目錄下建立IK的配置檔案:src/main/resouce/IKAnalyzer.cfg.xml
  2. 在配置檔案中增加配置擴充詞檔案的節點(如有多個,以“;”間隔):<entry key="ext_dict">ext.dic</entry>
  3. 在類目錄下建立擴充詞檔案 ext.dic
  4. 編輯該檔案加入新詞,一行一個(注意檔案要是UTF-8編碼)
Lucene分詞器詳解

8、IKAnalyzer.cfg.xml 檔案示例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 擴充配置</comment>
    
    <!--使用者可以在這裡配置自己的擴充字典 -->
    <entry key="ext_dict">ext.dic</entry>
    
    <!--使用者可以在這裡配置自己的擴充停止詞字典-->
    <entry key="ext_stopwords">my_ext_stopword.dic</entry>
</properties>