源码分析ik分词主流程

1、首先内存中建立词典树。

包括:主词典树、 停止词词典树 、量词词典树

数据结构:树 (或说字典树) ,子节点<=3时,数组存储DictSegment[] childrenArray;  >3时迁移到hashMap  Map<Character , DictSegment> childrenMap; 

根:DictSegment _MainDict = new DictSegment((char)0); 

字典树中的每个节点用DictSegmenter表示,每个节点的下一级节点分支使用Array或者Map来存储

主词典的子节点(也是子节点最多的节点)

segmentMapHashMap<K,V>  (id=76)

entrySetHashMap$EntrySet  (id=81)

keySetnull

loadFactor0.8

modCount5860

size5860

tableHashMap$Entry<K,V>[8192]  (id=85)

threshold6553

valuesnull

 

/**
 * 词典管理类,单例模式
 */
public class Dictionary {
	/*
	 * 词典单子实例
	 */
	private static Dictionary singleton;
	
	/*
	 * 主词典对象
	 */
	private DictSegment _MainDict;
	
	/*
	 * 停止词词典 
	 */
	private DictSegment _StopWordDict;
	/*
	 * 量词词典
	 */
	private DictSegment _QuantifierDict;
	
	/**
	 * 配置对象
	 */
	private Configuration cfg;
	
	。。。
	}

 

2、遍历子分词器分词

       对每个字符分别让三个分词器处理,如果符合字符类型等条件,将匹配到的词元加入结果集。

       依次遍历三个分词器:LetterSegmenter(字母分词),CN_QuantifierSegmenter(数、量词分词),CJKSegmenter(中文分词)

       CN_QuantifierSegmenter分出中文数字”一“,量词”个“

       LetterSegmenter:连续的英文字母作为一个词元,如:can

       第一次调用l = context.getNextLexeme()) == null,会分词,取词时候不为空。

 

/**
	 * 分词,获取下一个词元
	 * @return Lexeme 词元对象
	 * @throws IOException
	 */
	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;
	}

 

3、中文分词器analyz函数。

从主词典树查到第一个字符节点DictSegment,如果是完全匹配(单字词)加入结果集,如果是词的前缀,加入临时命中集tmpHits,下一个字符优先从tmpHits中匹配。

如果完全匹配(节点没有叶子),把词(路径)加入结果集,如果不匹配,从tmpHits移除。tmpHits匹配完后,还得去主词典再查是否为单字词或下一个词的前缀。

   

public void analyze(AnalyzeContext context) {
		
		if(CharacterUtil.CHAR_USELESS != context.getCurrentCharType()){ //zw:当前字符类型不是 无类型
			
			//优先处理tmpHits中的hit
			if(!this.tmpHits.isEmpty()){
				//处理词段队列
				Hit[] tmpArray = this.tmpHits.toArray(new Hit[this.tmpHits.size()]);
				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); //zw:主词典匹配singleton._MainDict
			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{
			//遇到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);
		}
	}
	/**
 * 表示一次词典匹配的命中
 */
public class Hit {
	//Hit不匹配
	private static final int UNMATCH = 0x00000000;
	//Hit完全匹配
	private static final int MATCH = 0x00000001;
	//Hit前缀匹配
	private static final int PREFIX = 0x00000010;
	
	
	//该HIT当前状态,默认未匹配
	private int hitState = UNMATCH;
	
	//记录词典匹配过程中,当前匹配到的词典分支节点
	private DictSegment matchedDictSegment; 
	/*
	 * 词段开始位置
	 */
	private int begin;
	/*
	 * 词段的结束位置
	 */
	private int end;
	
	。。。
	}
	

**
 * IK词元对象 
 */
public class Lexeme implements Comparable<Lexeme>{
	//lexemeType常量
	//未知
	public static final int TYPE_UNKNOWN = 0;
	//英文
	public static final int TYPE_ENGLISH = 1;
	//数字
	public static final int TYPE_ARABIC = 2;
	//英文数字混合
	public static final int TYPE_LETTER = 3;
	//中文词元
	public static final int TYPE_CNWORD = 4;
	//中文单字
	public static final int TYPE_CNCHAR = 64;
	//日韩文字
	public static final int TYPE_OTHER_CJK = 8;
	//中文数词
	public static final int TYPE_CNUM = 16;
	//中文量词
	public static final int TYPE_COUNT = 32;
	//中文数量词
	public static final int TYPE_CQUAN = 48;
	
	//词元的起始位移
	private int offset;
       //词元的相对起始位置
       private int begin;
       //词元的长度
       private int length;
       //词元文本
       private String lexemeText;
       //词元类型
       private int lexemeType;
       。。。
   }

 4、匹配到后向链表集合添加词元

 

/**
	 * 向链表集合添加词元
	 * @param lexeme
	 */
	boolean addLexeme(Lexeme lexeme){
		Cell newCell = new Cell(lexeme); 
		if(this.size == 0){
			this.head = newCell;
			this.tail = newCell;
			this.size++;
			return true;
			
		}else{
			if(this.tail.compareTo(newCell) == 0){//词元与尾部词元相同,不放入集合
				return false;
				
			}else if(this.tail.compareTo(newCell) < 0){//词元接入链表尾部
				this.tail.next = newCell;
				newCell.prev = this.tail;
				this.tail = newCell;
				this.size++;
				return true;
				
			}else if(this.head.compareTo(newCell) > 0){//词元接入链表头部
				this.head.prev = newCell;
				newCell.next = this.head;
				this.head = newCell;
				this.size++;
				return true;
				
			}else{					
				//从尾部上逆
				Cell index = this.tail;
				while(index != null && index.compareTo(newCell) > 0){
					index = index.prev;
				}
				if(index.compareTo(newCell) == 0){//词元与集合中的词元重复,不放入集合
					return false;
					
				}else if(index.compareTo(newCell) < 0){//词元插入链表中的某个位置
					newCell.prev = index;
					newCell.next = index.next;
					index.next.prev = newCell;
					index.next = newCell;
					this.size++;
					return true;					
				}
			}
		}
		return false;
	}


//分词结果集数据结构	
/**
 * IK分词器专用的Lexem快速排序集合
 */
class QuickSortSet {
	//链表头
	private Cell head;
	//链表尾
	private Cell tail;
	//链表的实际大小
	private int size;
	//内部类,QuickSortSet集合单元
	class Cell implements Comparable<Cell>{
		private Cell prev;
		private Cell next;
		private Lexeme lexeme;
		。。。
	 }
		。。。
	}
	

5、取词

     过滤停止词,设置词元文本。 

 

/**
	 * 返回lexeme 
	 * 
	 * 同时处理合并
	 * @return
	 */
	Lexeme getNextLexeme(){
		//从结果集取出,并移除第一个Lexme
		Lexeme result = this.results.pollFirst();
		while(result != null){
    		//数量词合并
    		this.compound(result);
    		if(Dictionary.getSingleton().isStopWord(this.segmentBuff ,  result.getBegin() , result.getLength())){
       			//是停止词继续取列表的下一个
    			result = this.results.pollFirst(); 				
    		}else{
	 			//不是停止词, 生成lexeme的词元文本,输出
	    		result.setLexemeText(String.valueOf(segmentBuff , result.getBegin() , result.getLength()));
	    		break;
    		}
		}
		return result;
	}

  

 

6、例子

 对"这是一个中文分词的例子,IKAnalyer can analysis english text too"分词结果如下:

0 - 2 : 这是 | CN_WORD  //0表示词的开始位置,2表示结束位置,这是 表示词的内容,CN_WORD表示中文

2 - 4 : 一个 | CN_WORD

2 - 3 : 一 | TYPE_CNUM  //TYPE_CNUM 表示中文数词

3 - 5 : 个中 | CN_WORD

3 - 4 : 个 | COUNT  //COUNT  表示 中文量词

4 - 6 : 中文 | CN_WORD

6 - 8 : 分词 | CN_WORD

9 - 11 : 例子 | CN_WORD

12 - 21 : ikanalyer | ENGLISH

22 - 25 : can | ENGLISH

26 - 34 : analysis | ENGLISH

35 - 42 : english | ENGLISH

43 - 47 : text | ENGLISH

48 - 51 : too | ENGLISH

 

这个例子使用非智能分词:细粒度输出所有可能的切分结果 。 智能分词: 合并数词和量词,对分词结果进行歧义判断

 

你可能感兴趣的:(源码分析)