Lucene的IK分词器学习,增加支持单个特殊符号搜索

前言

感谢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年以后几乎也没有修改。


posted @ 2024-06-11 15:27  mosakashaka  阅读(54)  评论(0编辑  收藏  举报