IK正向迭代最细粒度切分算法流程
一、 IK分词初始化
初始化最主要的工作就是读入词典,并将这些词放入内存字典树
1.main2012.dic(关键词)2.quantifier.dic(量词)3.stopword.dic(停用词)4.ext.dic(扩展词,可选)
http://blog.csdn.net/iamaboyy/article/details/7569977
二、 匹配
1. 主流程
主要的就是ik.next()方法:
1) 读入待匹配的文本
2) 初始化文本指针,指向文本中的第一个字符
3) 遍历分词器,进行分词处理,这里是最核心的流程之一,将待匹配文本生成分词候选集。
——子分词器
4) 处理完一个字符之后,文本指针后移一位,直到处理完所有待匹配文本
——歧义处理
5) 生成分词候选集之后,进行歧义处理,歧义处理方法区分智能和非智能。
其功能是根据分词候选集和歧义处理策略,生成最后的分词结果
2. IK的子分词器
子分词器才是真正的分词类,IK里面有三个子分词器
i. CJKSegmenter(中文分词),
ii. CN_QuantifierSegmenter(数量词分词),
iii.LetterSegmenter(字母分词)。
主分词器IKSegmentation遍历这三个分词器对文本输入流进行的分词处理。每种分词器的分词方法是独立的,
生成自己的分词结果,放到分词候选集里
这三个分词器的代码很类似,思路都是一样的,采用字典树(CJK使用)或其他简单数据结构(CN_QuantifierSegmenter和LetterSegmenter)匹配文本中的当前字符,将匹配到的字符加入到分词候选集
其他两个词典与其主要区别如下:
1. CN_QuantifierSegmenter的词典来源两个地方:1.quantifier.dic文件,包含量词 2.数词直接写到ChnNumberChars类中了,内容如下:"一二两三四五六七八九十零壹贰叁肆伍陆柒捌玖拾百千万亿拾佰仟萬億兆卅廿"
2. LetterSegmenter分别有三个类似的处理器:字母、数字、字母和数字的组合。
处理的基本思路就是匹配连续的相同类型字符,直到出现不同类型字符为止,切出一个词,比如LetterSegmenter对字串"中文abc英文"的处理方式就是匹配出连续的字母子串abc,切为一个词,切词结果为中文 abc 英文
三、对相交词进行歧义处理(judge方法)
四、其他
处理无法切分的非词典中词语,以及停用词
——http://blog.chinaunix.net/uid-20761674-id-3423941.html
这里有详细的流程图和url图
http://blog.163.com/liaoxiangui@126/blog/static/7956964020130299518177/
- Thread1——main(String[])
- Thread2——IKSegmenter.next()
- 测试类中IKSegmenter ik=new IKSegmenter(sr, false); //true代表调用IKSegmenter()构造函数时使用智能分词 调用IKSegmenter
- IKSegmenter.next()主流程最主要的就是.next()方法:
- //遍历子分词器 for(ISegmenter segmenter : segmenters){segmenter.analyze(context);}
- 子分词器for的第一次循环:处理英文,字母,阿拉伯字母,混合字母的analyze()
- 子分词器for的第二次循环:处理中文数词,中文量词的analyze()
- Thread3——CJKSegmenter.analyze(AnalyzeContext)
- 子分词器for的第三次循环:中文-日韩文子分词器的analyze()
- 重置子分词器for(ISegmenter segmenter : segmenters){ segmenter.reset();}
- 歧义处理this.arbitrator.process(context, this.cfg.useSmart());
测试代码:
import java.io.IOException; import java.io.StringReader; import org.wltea.analyzer.core.IKSegmenter; import org.wltea.analyzer.core.Lexeme; public class test2 { public static void main(String[] args) throws IOException { String text="23所"; StringReader sr=new StringReader(text); IKSegmenter ik=new IKSegmenter(sr, false); //true代表调用IKSegmenter()构造函数时使用智能分词 Lexeme lex=null; while((lex=ik.next())!=null){ System.out.print(lex.getLexemeText()+"|"); } } }
进入next()函数,遍历子分词器
public synchronized Lexeme next()throws IOException{ Lexeme l = null; 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; }
子分词器for的第一次循环:处理英文,字母,阿拉伯字母,混合字母的analyze()
public void analyze(AnalyzeContext context) { boolean bufferLockFlag = false; //处理英文字母 bufferLockFlag = this.processEnglishLetter(context) || bufferLockFlag; //处理阿拉伯字母 bufferLockFlag = this.processArabicLetter(context) || bufferLockFlag; //处理混合字母(这个要放最后处理,可以通过QuickSortSet排除重复) bufferLockFlag = this.processMixLetter(context) || bufferLockFlag; //判断是否锁定缓冲区 if(bufferLockFlag){ context.lockBuffer(SEGMENTER_NAME); }else{ //对缓冲区解锁 context.unlockBuffer(SEGMENTER_NAME); } }
子分词器for的第二次循环:处理中文数词,中文量词的analyze()
public void analyze(AnalyzeContext context) { //处理中文数词 this.processCNumber(context); //处理中文量词 this.processCount(context); //判断是否锁定缓冲区 if(this.nStart == -1 && this.nEnd == -1 && countHits.isEmpty()){ //对缓冲区解锁 context.unlockBuffer(SEGMENTER_NAME); }else{ context.lockBuffer(SEGMENTER_NAME); } }
子分词器for的第三次循环:中文-日韩文子分词器的analyze()
public void analyze(AnalyzeContext context) { if(CharacterUtil.CHAR_USELESS != context.getCurrentCharType()){ //如果content不是CHAR_USELESS字符————对应100行 //优先处理tmpHits中的hit if(!this.tmpHits.isEmpty()){ //处理词段队列 Hit[] tmpArray = this.tmpHits.toArray(new Hit[this.tmpHits.size()]); for(Hit hit : tmpArray){ // 把字符添加到hit中,并重新计算其匹配状态; matchWithHit是指从已匹配的Hit中直接取出DictSegment,继续向下匹配 hit = Dictionary.getSingleton().matchWithHit(context.getSegmentBuff(), context.getCursor() , hit); if(hit.isMatch()){ //begin-->end+字符 是一个完整的词,输出这个词; Lexeme newLexeme = new Lexeme(context.getBufferOffset() , hit.getBegin() , context.getCursor() - hit.getBegin() + 1 , Lexeme.TYPE_CNWORD); context.addLexeme(newLexeme); if(!hit.isPrefix()){ //begin-->end+字符 不是词前缀,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 Lexeme(context.getBufferOffset() , context.getCursor() , 1 , Lexeme.TYPE_CNWORD); context.addLexeme(newLexeme); //同时也是词前缀 if(singleCharHit.isPrefix()){ //前缀匹配则放入hit列表 this.tmpHits.add(singleCharHit); } }else if(singleCharHit.isPrefix()){ //首字为词前缀 //前缀匹配则放入hit列表 this.tmpHits.add(singleCharHit); } } else{ //如果content是CHAR_USELESS字符 //清空队列 this.tmpHits.clear(); } //判断缓冲区是否已经读完 if(context.isBufferConsumed()){ //清空队列 this.tmpHits.clear(); } //判断是否锁定缓冲区 if(this.tmpHits.size() == 0){ context.unlockBuffer(SEGMENTER_NAME); }else{ context.lockBuffer(SEGMENTER_NAME); } }
在此发现IK中的userSmart配置 在solr4.0中不生效
由于版本而导致的问题
首先是中文分词器。中文分词库注定悲剧的地方在于总要跟着国外开源软件的更新走,一旦lucene,solr更新了,某些基础类改变或者接口变 动,分词器可能就不再支持了。IKTokenizerFactory继承自solr的BaseTokenizerFactory,但是 BaseTokenizerFactory在solr3.6之后版本已经不存在了,如果你按照以前的配置方式在scheme里配置:
<fieldType name="text" class="solr.TextField" >
<analyzer type="index">
<tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory" useSmart ="false"/>
</analyzer>
<analyzer type="query"> <tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory" useSmart ="false"/>
</analyzer>
</fieldType>
启动solr后报错提示BaseTokenizerFactory类是找不到了的。
http://blog.csdn.net/pelick/article/details/8469442
解决办法:
https://code.google.com/p/ik-analyzer/issues/detail?id=125
https://code.google.com/p/ik-analyzer/issues/detail?id=91