IK分词器原理研磨及源码改造,更适用生产

简介

IK分词全名为IK Analyzer

IK分词是一款国人作者林良益开发的相对简单的中文分词器,IKAnalyzer 是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出 了3个大版本。最初,它是以开源项目 Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为 面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。在这里插入图片描述

IK 这个名字是来源于暗黑破坏神2这款游戏,它是游戏中武器装备的名字。刚好我在做这个分词器的时候,我也在玩这款游戏,而且刚好打到这个装备,很开心。我们想想 Java 也是开发人员在开发的过程中正好在喝咖啡,所以就叫了 Java 这个名字。所以那时候我也在想,给这个分词器命名的话,我就把它命名为 Immortal King,中文名叫不朽之王,是那个装备的名称。这个名字就是这么来的。

分析结构

在这里插入图片描述

IKAnalzyerDemo.java

//构建IK分词器,使用smart分词模式
       Analyzer analyzer = new IKAnalyzer(true);    
       //获取Lucene的TokenStream对象
        TokenStream ts = null;
       try {
           ts= analyzer.tokenStream("myfield",newStringReader("这是一个中文分词的例子,你可以直接运行它!IKAnalyer can analysis english text too"));
           //获取词元位置属性
           OffsetAttribute  offset = ts.addAttribute(OffsetAttribute.class);
           //获取词元文本属性
           CharTermAttribute term = ts.addAttribute(CharTermAttribute.class);
           //获取词元文本属性
           TypeAttribute type = ts.addAttribute(TypeAttribute.class); 
           //重置TokenStream(重置StringReader)
           ts.reset();
           //迭代获取分词结果
           while (ts.incrementToken()) {
             System.out.println(offset.startOffset()+" - "+ offset.endOffset() +" : " + term.toString() + " | " + type.type());
           }
           //关闭TokenStream(关闭StringReader)
           ts.end();   // Performend-of-stream operations, e.g. set the final offset.
       }catch(IOExceptione) {
           e.printStackTrace();
       }finally{
           //释放TokenStream的所有资源
           if(ts !=null){
             try {
              ts.close();
             } catch (IOException e) {
              e.printStackTrace();
             }
           }
        }
运行结果:
Load extended dictionary:ext.dic
Load stopwords dictionary:stopword.dic
0 - 2 : 这是 | CN_WORD
2 - 4 : 一个 | CN_WORD
4 - 6 : 中文 | CN_WORD
6 - 8 : 分词 | CN_WORD
8 - 9 : 的 | CN_WORD
9 - 11 : 例子 | CN_WORD
12 - 13 : 你 | CN_WORD
13 - 15 : 可以 | CN_WORD
15 - 17 : 直接 | CN_WORD
17 - 19 : 运行 | CN_WORD
19 - 20 : 它 | CN_CHAR
21 - 30 : ikanalyer | ENGLISH
31 - 34 : can | ENGLISH
35 - 43 : analysis | ENGLISH
44 - 51 : english | ENGLISH
52 - 56 : text | ENGLISH
57 - 60 : too | ENGLISH

这里可以看出来大体的一个流程

  • 构件一个分词器,策略是smart模式
  • 加载词典(主词典、扩展词典、量词词典、停止词词典)
  • 分词解析
  • 输出词

下面介绍下最核心的分词部分

/**
     * 分词,获取下一个词元
     *
     * @return Lexeme 词元对象
     */
    public synchronized Lexeme next() throws IOException {
        Lexeme l;
        while ((l = context.getNextLexeme()) == null) {
            /*
             * 从reader中读取数据,填充buffer
             * 如果reader是分次读入buffer的,那么buffer要  进行移位处理
             * 移位处理上次读入的但未处理的数据
             */
            int available = context.fillBuffer(this.input);
            if (available <= 0) {
                //reader已经读完
                context.reset();
                return null;

            } else {
                //初始化指针
                context.initCursor();
                do {
                    //遍历子分词器
                    for (ISegmenter segmenter : segmenters) {
                        segmenter.analyze(context);
                    }
                    //字符缓冲区接近读完,需要读入新的字符
                    if (context.needRefillBuffer()) {
                        break;
                    }
                    //向前移动指针
                } while (context.moveCursor());
                //重置子分词器,为下轮循环进行初始化
                for (ISegmenter segmenter : segmenters) {
                    segmenter.reset();
                }
            }
            //对分词进行歧义处理
            this.arbitrator.process(context, this.cfg.useSmart());
            //将分词结果输出到结果集,并处理未切分的单个CJK字符
            context.outputToResult();
            //记录本次分词的缓冲区位移
            context.markBufferOffset();
        }
        return l;
    }

intavailable = context.fillBuffer(this.input);

此处是用来读取待处理的文本信息

遍历分词器,进行分词处理,这里是最核心的流程之一,将待匹配文本生成分词候选集。

总共有三种分词器CJKSegmenter(中文、日韩分词器)、CN_QuantifierSegment(数量词分词器)、LetterSegment(字母数字分词器),每种分词器的分词方法是独立的,各自生成自己的分词结果,放到分词候选集里

 for(ISegmenter segmenter : segmenters){
                   segmenter.analyze(context);
 } 

//对分词进行歧义处理

this.arbitrator.process(context,this.cfg.useSmart());

生成分词候选集之后,进行歧义处理,歧义处理方法区分智能和非智能,也就是在初始化IKSegment时传递的第二个参数IKSegmenter(Readerinput, boolean useSmart)。

其功能是根据分词候选集和歧义处理策略,生成最后的分词结果,具体策略后面介绍

//记录本次分词的缓冲区位移

 context.markBufferOffset();

分词器

IK里的分词器主要是三个分词器:CJKSegmenter(中文分词),CN_QuantifierSegmenter(数量词分词),LetterSegmenter(字母分词)。这三个分词器都继承了ISegmenter接口,思路相差不大,其中采用的结构也比较容易理解,采用字典树(CJK使用)或其他简单数据结构(CN_QuantifierSegmenter和LetterSegmenter)匹配文本中的当前字符,将匹配到的字符加入到分词候选集

其中CJKSegmenter中的核心代码analyze方法的代码如下:

public void analyze(AnalyzeContext context) {
		if(CharacterUtil.CHAR_USELESS != context.getCurrentCharType()){
			//优先处理tmpHits中的hit
			if(!this.tmpHits.isEmpty()){
				//处理词段队列
				Hit[] tmpArray = this.tmpHits.toArray(new Hit[0]);
				for(Hit hit : tmpArray){
					hit = Dictionary.getSingleton().matchWithHit(context.getSegmentBuff(), context.getCursor() , hit);
					if(hit.isMatch()){
						//输出当前的词
						Lexeme newLexeme = new Lexeme(context.getBufferOffset() , hit.getBegin() , context.getCursor() - hit.getBegin() + 1 , Lexeme.TYPE_CNWORD);
						context.addLexeme(newLexeme);			
						if(!hit.isPrefix()){//不是词前缀,hit不需要继续匹配,移除
							this.tmpHits.remove(hit);
						}						
					}else if(hit.isUnmatch()){
						//hit不是词,移除
						this.tmpHits.remove(hit);
					}					
				}
			}						
			//*********************************
			//再对当前指针位置的字符进行单字匹配
			Hit singleCharHit = Dictionary.getSingleton().matchInMainDict(context.getSegmentBuff(), context.getCursor(), 1);
			if(singleCharHit.isMatch()){//首字成词
				//输出当前的词
				Lexeme newLexeme = new 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值