Lucene的IK分詞器學習,增加支援單個特殊符號搜尋

mosakashaka發表於2024-06-11

前言

感謝CSDN這篇文章,原始程式碼基於這裡。

正常對於“abc@google.com”這段文字,搜尋'@'這個符號是搜不出來的。本文主要修改是擴充套件IK分詞器,增加了對諸如"@ -"這種特殊文字的檢索。

當然這個其實並沒有多少實際意義,所以基本也是出於學習的目的。

正文

IK分詞器分析

這裡不深入原理,只涉及到修改時需要考慮的地方。

  • 分詞邏輯:IKAnalyzer->IKTokenizer->IKSegmenter,最終實際是IKSegmenter中的next()方法對輸入流進行分詞,獲取下一個分詞(也叫詞元Lexeme)。
  • IKSegementer分詞:包含三個子分詞器,實現ISegmenter介面,依次呼叫,彼此獨立分詞(既一段文字可以被不通分詞器重複分詞)
    • LetterSegmenter: 識別字母、數字、符號的組合
    • CN_QuantifierSegmenter: 識別中文量詞分詞,如一條三艘
    • CJKSegmenter: IK的核心,中文分詞,透過從字典資料中查詢匹配分詞。不過不是本文的核心了。
  • 子分詞器邏輯:AnalyzeContext為輸入引數,儲存輸入流資訊,子分詞器解析每個字元,透過字元型別判斷詞元開始和結尾。當一個分詞結束,透過AnalyzeContext提供的addLexeme方法增加一個分詞(詞元)
  • IKArbitrator歧義裁決:其實這裡涉及到一個IK分詞器的smart引數,表面上,配置smarttrue時,分詞比較粗,為false時分詞粒度更細,這個引數影響的就是這個裁決器。當擁有smarttrue時,多個分詞如果彼此有覆蓋關係,會自動進行合併,如熱反應堆,原本會被拆分成反應``反應堆``堆``熱反應堆等多個詞,但是如果配置了smart,由於熱反應堆本身包含了其他詞,則最後分詞結果只會有一個熱反應堆詞元。所以一般來說,建立索引的時候,不配置smart,而搜尋前分詞的時候,可以配置smart。有點扯遠了。

修改說明

核心思路:在原有的分詞器中,建立一個資訊的分詞器SpecialCharSegmenter,當碰到特殊字元時,插入一個新的Lexeme

由於原始的幾個類IKAnalyzer IKTokenizer IKSegmenter等都是final的,所以我直接單獨建立了類MyIKAnalyzer,MyIKTokenizer,MyIKSegmenter替代這幾個類。

比較重要的修改點說明如下:

MyCharacterUtil

擴充套件原有CharacterUtil工具類,增加特殊字元型別值,在解析字元流時,識別該型別

 //需要進行索引的特殊字元
    public static final int CHAR_SPECIAL = 0X00000010;

    //需要進行索引的特殊字元,可以擴充修改
    private static final char[] Letter_Special = new char[]{'#', '-', '@', '.'};

    static int identifyCharType(char input) {
           .......省略非關鍵部分
            } else if (isSpecialChar(input)) {
                return CHAR_SPECIAL;
            }
    }

    //特殊字元的判斷
    static boolean isSpecialChar(char input) {
        int index = Arrays.binarySearch(Letter_Special, input);
        return index >= 0;
    }

MyAnalyzeContext

讀取字元流的工具類,原本呼叫CharacterUtil,修改為呼叫我們自定義的MyCharacterUtil

    /**
     * 初始化buff指標,處理第一個字元
     */
    void initCursor() {
        this.cursor = 0;
        this.segmentBuff[this.cursor] = CharacterUtil.regularize(this.segmentBuff[this.cursor]);
        //修改點
        this.charTypes[this.cursor] = MyCharacterUtil.identifyCharType(this.segmentBuff[this.cursor]);
    }

    /**
     * 指標+1 成功返回 true; 指標已經到了buff尾部,不能前進,返回false 並處理當前字元
     */
    boolean moveCursor() {
        if (this.cursor < this.available - 1) {
            this.cursor++;
            this.segmentBuff[this.cursor] = CharacterUtil.regularize(this.segmentBuff[this.cursor]);
            //修改點
            this.charTypes[this.cursor] = MyCharacterUtil.identifyCharType(this.segmentBuff[this.cursor]);
            return true;
        } else {
            return false;
        }
    }

MyIKSegmenter

loadSegmenters中,載入自定義的分詞器

        // 處理特殊字元的子分詞器
        segmenters.add(new SpecialCharSegmenter());

SpecialCharSegmenter

核心是碰到特殊字元,增加一個詞元,邏輯非常簡單。

    public void analyze(AnalyzeContext context) {
        if (MyCharacterUtil.CHAR_SPECIAL == context.getCurrentCharType()) {
            // 遇到特殊字元,輸出詞元
            Lexeme newLexeme = new Lexeme(context.getBufferOffset(), context.getCursor(), 1,
                    Lexeme.TYPE_LETTER);
            context.addLexeme(newLexeme);
        }
    }

CustomLetterSegmenter

擴充套件原有的LetterSegmenter,之所以要修改,是發現該類會將部分特殊符號,如abc@google.com識別為一個整體類,而識別的方式是透過字元型別為CHAR_USELESS

當我修改了字元解析時,為特殊符號定義了新的型別CHAR_SPECIAL,所以需要修改processMixLetter:

//原始
        } else if ((CharacterUtil.CHAR_USELESS == context.getCurrentCharType())
                    && this.isLetterConnector(context.getCurrentChar())) {
                // 記錄下可能的結束位置
                this.end = context.getCursor();
            } else {

//修改為
        } else if ((CharacterUtil.CHAR_USELESS == context.getCurrentCharType()
                    //可能被修改為了特殊字元型別
                    || MyCharacterUtil.CHAR_SPECIAL == context.getCurrentCharType())
                    && this.isLetterConnector(context.getCurrentChar())) {
                // 記錄下可能的結束位置
                this.end = context.getCursor();
            } else {

驗證

程式碼上傳在 github https://github.com/mosakashaka/wbdemos/tree/master/lucene-ik-demo

直接執行後透過地址http://localhost:8080/lucene/createIndex,建立普通索引(不支援符號),索引中自動新增了幾條資料,其中包含了@符號。

此時透過搜尋介面http://localhost:8080/test/searchText?text=@檢索,搜尋不到資料。

如果透過http://localhost:8080/lucene/createIndex2(地址後多了2),則會建立支援符號的索引,此時搜尋@ -等符號則可以搜尋到。

結語

本身可能意義不大,純粹是學習的目的。

IK原始的程式碼看起來很久了,我看了下ES用的那個IK分詞器的提交記錄,16年以後幾乎也沒有修改。


相關文章