【Lucene】分詞器詳解,常用的分詞器,IKANalyzer

1. 分詞器詳解

1.1 分詞器的作用

  • 在創建索引的時候需要用到分詞器,在使用字符串搜索的時候也會用到分詞器,並且這兩個地方要使用同一個分詞器,否則可能會搜索不出來結果。

  • 分詞器(Analyzer)的作用是把一段文本中的詞按規則取出所包含的所有詞,對應的是Analyzer類,這是一個抽象類(public abstract class org.apache.lucene.analysis.Analyzer),切分詞的具體規則是由子類實現的,所以對於不同的語言規則,要有不同的分詞器。

下面我們看看哪裏使用了分詞器。

    public void createIndex() throws IOException {
        //1.創建一個Directory對象,指定索引庫保存的位置。
        // 把索引庫保存在內存中
        //Directory directory = new RAMDirectory();
        // 把索引庫保存在磁盤
        Directory directory = FSDirectory.open(new File("E:\\practice\\lucene\\directory").toPath());
        //2.基於Directory對象創建一個IndexWriter對象
        IndexWriter indexWriter = new IndexWriter(directory,new IndexWriterConfig());
        //3.讀取磁盤上的文件,對應每個文件創建一個文檔對象。
        File dir = new File("C:\\Users\\siyi\\Desktop\\searchsource");
        File[] files = dir.listFiles();
        for(File file : files){
            //取文件名
            String fileName = file.getName();
            //文件的路徑
            String filePath = file.getPath();
            //文件的內容
            String fileContent = FileUtils.readFileToString(file,"utf-8");
            //文件的大小
            long fileSize = FileUtils.sizeOf(file);
            //4.向文檔對象中添加域
            //創建Field
            //參數1:域的名稱 參數2:域的內容 參數3:是否存儲
            Field fieldName = new TextField("name", fileName, Field.Store.YES);
            Field fieldPath = new StoredField("path", filePath);
            Field fieldContent = new TextField("content",fileContent,Field.Store.YES);
            Field fieldSizeValue = new LongPoint("size",fileSize);
            Field fieldSizeStore = new StoredField("size",fileSize);
            //創建文檔對象
            Document document = new Document();
            document.add(fieldName);
            document.add(fieldPath);
            document.add(fieldContent);
            document.add(fieldSizeValue);
            document.add(fieldSizeStore);
            //5.把文檔對象寫入索引庫
            indexWriter.addDocument(document);
        }
        //6.關閉indexWriter對象
        indexWriter.close();
    }

上述程序是一個很普通的構建索引案例。但是我們在程序中並沒又發現有分詞器的存在。
那是因爲我們在創建IndexWriterConfig對象時,默認會使用標準分詞器(StandardAnalyzer)。

1.2 分詞器API

1.2.1 示例

如果我們想知道分詞器分詞後的結果,那麼我們可以根據下面的程序得到:

    public void testTokenStream() throws Exception {
        //1.創建一個Analyzer對象,StandardAnalyzer對象
        Analyzer analyzer = new StandardAnalyzer();
        //2.使用分析器對象的tokenStream方法獲得一個TokenStream對象
        TokenStream tokenStream = analyzer.tokenStream("", "Lucene 是 apache 軟件基金會的一個子項目,由 Doug Cutting 開發,是一個開放源代碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的庫,提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene 的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此爲基礎建立起完整的全文檢索引擎。Lucene 是一套用於全文檢索和搜尋的開源程式庫,由 Apache 軟件基金會支持和提供。");
        //3.向TokenStream對象中設置一個引用,相當於一個指針
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        //4.調用TokenStream對象的rest方法。如果不調用拋異常
        tokenStream.reset();
        //5.使用while循環遍歷TokenStream對象
        while(tokenStream.incrementToken()){
            System.out.println(charTermAttribute.toString());
        }
        //6.關閉TokenStream對象
        tokenStream.close();
    }

運行結果如下:

我們會發現標準分詞器對英文的支持是相當好的。但是對中文卻並不支持,標準分詞器(StandardAnalyzer)會將每個字都分成一個詞,但是我們檢索的過程中並不會一個字一個字的查。所以我們需要其他的分詞器。

但是在使用其他分詞器前我們需要先了解分詞器相應的API和原理。

1.2.2 Analyzer

分析器,分詞器組件的核心API,它的職責:構建真正對文本進行分詞處理的TokenStream(分詞處理器)。通過調用它的如下兩個方法(這兩個方法是final方法,不能被覆蓋的。),得到輸入文本的分詞處理器。

  public final TokenStream tokenStream(final String fieldName,
                                       final Reader reader) {
    TokenStreamComponents components = reuseStrategy.getReusableComponents(this, fieldName);
    final Reader r = initReader(fieldName, reader);
    if (components == null) {
      components = createComponents(fieldName);
      reuseStrategy.setReusableComponents(this, fieldName, components);
    }
    components.setReader(r);
    return components.getTokenStream();
  }

  public final TokenStream tokenStream(final String fieldName, final String text) {
    TokenStreamComponents components = reuseStrategy.getReusableComponents(this, fieldName);
    @SuppressWarnings("resource") final ReusableStringReader strReader = 
        (components == null || components.reusableStringReader == null) ?
        new ReusableStringReader() : components.reusableStringReader;
    strReader.setValue(text);
    final Reader r = initReader(fieldName, strReader);
    if (components == null) {
      components = createComponents(fieldName);
      reuseStrategy.setReusableComponents(this, fieldName, components);
    }

    components.setReader(r);
    components.reusableStringReader = strReader;
    return components.getTokenStream();
  }
  • 問題1:從哪裏得到了TokenStream?
    從components.getTokenStream()得到了TokenStream
  • 問題2:方法傳入的字符流Reader 給了誰?
    方法傳入的字符流Reader 最終給了Tokenizer的inputPending(類型:Reader):initReader(fieldName, reader)-components.setReader®-source.setReader(reader)-this.inputPending = input;
  • 問題3: components是什麼?components的獲取邏輯是怎樣?
    components是分詞處理的組件,components的獲取邏輯是有就直接拿來用,沒有就新建一個,後面都用新建的這一個
  • 問題4:createComponents(fieldName) 方法是個什麼方法?
    是創建分詞處理組件的方法
  • 問題5:Analyzer能直接創建對象嗎?
    Analyzer是一個抽象類,不能直接創建對象
  • 問題6:爲什麼它要這樣設計?
    使用裝飾器模式方便擴展
  • 問題7:Analyzer的實現子類有哪些?
  • 問題8:要實現一個自己的Analyzer,必須實現哪個方法?
    必須實現protected abstract TokenStreamComponents createComponents(String fieldName);

1.2.3 createComponents(String fieldName)

  protected abstract TokenStreamComponents createComponents(String fieldName);

是Analizer中唯一的抽象方法,擴展點。通過提供該方法的實現來實現自己的Analyzer。

參數說明:fieldName,如果我們需要爲不同的字段創建不同的分詞處理器組件,則可根據這個參數來判斷。否則,就用不到這個參數。

返回值爲 TokenStreamComponents 分詞處理器組件。

我們需要在createComponents方法中創建我們想要的分詞處理器組件。

1.2.4 TokenStreamComponents

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

  • 問題1:這個類的構造方法有幾個?區別是什麼?從中能發現什麼?
    兩個構造方法:
    public TokenStreamComponents(final Tokenizer source,
        final TokenStream result) {
      this.source = source;
      this.sink = result;
    }
    
    public TokenStreamComponents(final Tokenizer source) {
      this.source = source;
      this.sink = source;
    }

區別是參數不一樣,可以發現source和sink有繼承關係

  • 問題2:source 和 sink屬性分別是什麼類型?這兩個類型有什麼關係?
Tokenizer source
TokenStream sink
Tokenizer是TokenStream的子類
  • 在這個類中沒有創建source、sink對象的代碼(而是由構造方法傳入)。也就是說我們在Analyzer.createComponents方法中創建它的對象前,需先創建source、sink

  • 問題4:在Analyzer中tokenStream() 方法中把輸入流給了誰?得到的TokenStream對象是誰?TokenStream對象sink中是否必須封裝有source對象?
    輸入流給了source.setReader(reader),得到的TokenStream對象是TokenStream sink

components.getTokenStream()

public TokenStream getTokenStream() {
	return sink;
}

1.2.5 TokenStream

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

  • 問題1:TokenStream對象sink中是否必須封裝有source對象,TokenStream中有沒有對應的給入方法?
    沒有

  • 問題2:TokenStream是一個抽象類,有哪些方法,它的抽象方法有哪些?它的構造方法有什麼特點?
    抽象方法:

    public abstract boolean incrementToken() throws IOException;
    

    構造方法有兩個:

     protected TokenStream(AttributeSource input) {
        super(input);
        assert assertFinal();
      }
     
    
    protected TokenStream(AttributeFactory factory) {
        super(factory);
        assert assertFinal();
      }
    

    概念說明:Token: 分項,從字符流中分出一個一個的項

  • 問題3:TokenStream的具體子類分爲哪兩類?有什麼區別?
    Tokenizer:分詞器,輸入是Reader字符流的TokenStream,完成從流中分出分項
    TokenFilter:分項過濾器,它的輸入是另一個TokenStream,完成對從上一個TokenStream中流出的token的特殊處理。

  • 問題4:TokenStream繼承了誰?它是幹什麼用的?
    繼承了AttributeSource,TokenStream進行分詞處理的
    概念說明:Token Attribute: 分項屬性(分項的信息):如 包含的詞、位置等

1.2.6 TokenStream 的兩類子類

Tokenizer:分詞器,輸入是Reader字符流的TokenStream,完成從流中分出分項

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

  • 問題1:這個類該如何使用?要實現自己的Tokenizer只需要做什麼?
    要實現自己的Tokenizer只需要繼承Tokenizer複寫incrementToken()方法

  • 問題2:請查看TokenFilter類的源碼及註釋,如何實現自己的TokenFilter?
    要實現自己的TokenFilter只需要繼承TokenFilter複寫incrementToken()方法

  • 問題3:TokenFilter的子類有哪些?
    在這裏插入圖片描述

  • 問題4:TokenFilter是不是一個典型的裝飾器模式?如果我們需要對分詞進行各種處理,只需要按我們的處理順序一層層包裹即可(每一層完成特定的處理)。不同的處理需要,只需不同的包裹順序、層數。

1.2.7 TokenStream 繼承了 AttributeSource

  • 問題1:我們在TokenStream及它的兩個子類中是否有看到關於分項信息的存儲,如該分項的詞是什麼、這個詞的位置索引?

概念說明:Attribute 屬性 Token Attribute 分項屬性(分項信息),如 分項的詞、詞的索引位置等等。這些屬性通過不同的Tokenizer /TokenFilter處理統計得出。不同的Tokenizer/TokenFilter組合,就會有不同的分項信息。它是會動態變化的,你不知道有多少,是什麼。那該如何實現分項信息的存儲呢?

答案就是 AttributeSource、Attribute 、AttributeImpl、AttributeFactory

1、AttribureSource 負責存放Attribute對象,它提供對應的存、取方法

2、Attribute對象中則可以存儲一個或多個屬性信息

3、AttributeFactory 則是負責創建Attributre對象的工廠,在TokenStream中默認使用了AttributeFactory.getStaticImplementation 我們不需要提供,遵守它的規則即可。

1.2.8 AttributeSource使用規則說明

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

<T extends Attribute> T addAttribute(Class<T> attClass) 該方法要求傳人你需要添加的屬性的接口類(繼承Attribute),返回對應的實現類實例給你。從接口到實例,這就是爲什麼需要AttributeFactory的原因。這個方法是我們常用的方法

void addAttributeImpl(AttributeImpl att)
  • 加入的每一個Attribute實現類在AttributeSource中只會有一個實例,分詞過程中,分項是重複使用這一實例來存放分項的屬性信息。重複調用add方法添加它返回已存儲的實例對象。

  • 要獲取分項的某屬性信息,則需持有某屬性的實例對象,通過addAttribute方法或getAttribure方法獲得Attribute對象,再調用實例的方法來獲取、設置值

  • 在TokenStream中,我們用自己實現的Attribute,默認的工廠。當我們調用這個add方法時,它怎麼知道實現類是哪個?這裏有一定規則要遵守:

    1、自定義的屬性接口 MyAttribute 繼承 Attribute

    2、自定義的屬性實現類必須繼承 Attribute,實現自定義的接口MyAttribute

    3、自定義的屬性實現類必須提供無參構造方法

    4、爲了讓默認工廠能根據自定義接口找到實現類,實現類名需爲:接口名+Impl 。

1.2.9 TokenStream 的使用步驟。

我們在應用中並不直接使用分詞器,只需爲索引引擎和搜索引擎創建我們想要的分詞器對象。但我們在選擇分詞器時,會需要測試分詞器的效果,就需要知道如何使用得到的分詞處理器TokenStream,使用步驟:

1、從tokenStream獲得你想要獲得分項屬性對象(信息是存放在屬性對象中的)

2、調用 tokenStream 的 reset() 方法,進行重置。因爲tokenStream是重複利用的。

3、循環調用tokenStream的incrementToken(),一個一個分詞,直到它返回false

4、在循環中取出每個分項你想要的屬性值。

5、調用tokenStream的end(),執行任務需要的結束處理。

6、調用tokenStream的close()方法,釋放佔有的資源。

2. 常用的分詞器

分詞和查詢都是以詞項爲基本單位,詞項是詞條化的結果。在Lucene中分詞主要依靠Analyzer類解析實現。Analyzer類是一個抽象類,分詞的具體規則是由子類實現的,所以對於不同的語言規則,要有不同的分詞器

  1. StopAnalyzer
    停用詞分詞器:能過濾詞彙中的特定字符串和詞彙,並且完成大寫轉小寫的功能。

  2. StandardAnalyzer
    標準分詞器:根據空格和符號來完成分詞,還可以完成數字、字母、E-mail地址、IP地址以及中文字符的分析處理,還可以支持過濾詞表,用來代替StopAnalyzer能夠實現的過濾功能。

  3. WhitespaceAnalyzer
    空格分詞器:使用空格作爲間隔符的詞彙分割分詞器。處理詞彙單元的時候,以空格字符作爲分割符號。分詞器不做詞彙過濾,也不進行小寫字符轉換。實際中可以用來支持特定環境下的西文符號的處理。由於不完成單詞過濾和小寫字符轉換功能,也不需要過濾詞庫支持。詞彙分割策略上簡單使用非英文字符作爲分割符,不需要分詞詞庫支持。

  4. SimleAnalyzer
    簡單分詞:具備基本西文字符詞彙分析的分詞器,處理詞彙單元時,以非字母字符作爲分割符號。分詞器不能做詞彙的過濾,之進行詞彙的分析和分割。輸出地詞彙單元完成小寫字符轉換,去掉標點符號等分割符。

  5. CJKAnalyzer
    二分法分詞:內部調用CJKAnalyzer分詞器,對中文進行分詞,同時使用StopFilt過濾器完成過濾功能,可以實現中文的多元切分和停用詞過濾。

  6. IKAnalyzer
    IKAnalyzer實現了以詞典爲基礎的正反向全切分,以及正反向最大匹配切分兩種方法。IKAnalyzer是第三方實現的分詞器,繼承自Lucene的Analyzer類,針對中文文本進行處理。

  7. Paoding Analysis
    Paoding Analysis中文分詞具有極 高效率 和 高擴展性。引入隱喻,採用完全的面向對象設計,構思先進。其效率比較高,在PIII 1G內存個人機器上,1秒可準確分詞100萬漢字。採用基於不限制個數的詞典文件對文章進行有效切分,使能夠將對詞彙分類定義。能夠對未知的詞彙進行合理解析。

  8. MMSeg4J
    mmseg4j 用 Chih-Hao Tsai 的 MMSeg 算法實現的中文分詞器(http://technology.chtsai.org/mmseg/ ),並實現 lucene 的 analyzer 和 solr 的TokenizerFactory 以方便在Lucene和Solr中使用。 MMSeg 算法有兩種分詞方法:Simple和Complex,都是基於正向最大匹配。Complex 加了四個規則過慮。官方說:詞語的正確識別率達到了 98.41%。mmseg4j 已經實現了這兩種分詞算法。

3. IKAnalyzer

因爲我這兒指的IKAnalyzer是國內某大佬開發的,所以找不到依賴座標。

  • IKAnalyzer相關文件下載:
    鏈接: https://pan.baidu.com/s/1jyhKVG0NQoW3OdQBV92_kg 提取碼: 3bjq

  • 使用方法:
    第一步:把jar包添加到工程中
    第二步:把配置文件和擴展詞典和停用詞詞典添加到classpath下

  • 注意:hotword.dic和ext_stopword.dic文件的格式爲UTF-8,注意是無BOM 的UTF-8 編碼。
    也就是說禁止使用windows記事本編輯擴展詞典文件

    public void testTokenStream2() throws Exception {
        //1.創建一個Analyzer對象,StandardAnalyzer對象
        Analyzer analyzer = new IKAnalyzer();
        //2.使用分析器對象的tokenStream方法獲得一個TokenStream對象
        TokenStream tokenStream = analyzer.tokenStream("", "Linux 剛面世時並沒有圖形界面,所有的操作全靠命令完成,如 磁盤操作、文件存取、目錄操作、進程管理、文件權限 設定等");
        //3.向TokenStream對象中設置一個引用,相當於一個指針
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        //4.調用TokenStream對象的rest方法。如果不調用拋異常
        tokenStream.reset();
        //5.使用while循環遍歷TokenStream對象
        while(tokenStream.incrementToken()){
            System.out.println(charTermAttribute.toString());
        }
        //6.關閉TokenStream對象
        tokenStream.close();
    }

運行結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章