ik分词学习
词典结构。
1.词典是以树状结构存储的。
2.每个节点是一个字。
3.每个节点还包含所有子节点的集合。
4.最根节点是(char)0
5. 每个节点中存贮子结点的方式有两种
//Map存储结构 子节点个数>3
private Map<Character , DictSegment> childrenMap;
//数组方式存储结构 字节点个数<=3
private DictSegment[] childrenArray;
6.每个节点用 private Character nodeChar; 标示
7.当前节点存储的Segment数目 private int storeSize = 0; 递增
8.当前DictSegment状态 ,默认 0 , 1表示从根节点到当前节点的路径表示一个词
private int nodeState = 0;
*******************************************************************
装载词库
public synchronized void fillSegment(char[] charArray , int begin , int length){
//获取字典表中的汉字对象
Character beginChar = new Character(charArray[begin]);
Character keyChar = charMap.get(beginChar);
//字典中没有该字,则将其添加入字典
if(keyChar == null){
charMap.put(beginChar, beginChar);
keyChar = beginChar;
}
//搜索当前节点的存储,查询对应keyChar的keyChar,如果没有则创建
DictSegment ds = lookforSegment(keyChar);
//处理keyChar对应的segment
if(length > 1){
//词元还没有完全加入词典树
ds.fillSegment(charArray, begin + 1, length - 1);
}else if (length == 1){
//已经是词元的最后一个char,设置当前节点状态为1,表明一个完整的词
ds.nodeState = 1;
}
}
/**
* 查找本节点下对应的keyChar的segment
* 如果没有找到,则创建新的segment
* @param keyChar
* @return
*/
private DictSegment lookforSegment(Character keyChar){
DictSegment ds = null;
if(this.storeSize <= ARRAY_LENGTH_LIMIT){
//获取数组容器,如果数组未创建则创建数组
DictSegment[] segmentArray = getChildrenArray();
//搜寻数组
for(DictSegment segment : segmentArray){
if(segment != null && segment.nodeChar.equals(keyChar)){
//在数组中找到与keyChar对应的segment
ds = segment;
}
}
//遍历数组后没有找到对应的segment
if(ds == null){
//构造新的segment
ds = new DictSegment(keyChar);
if(this.storeSize < ARRAY_LENGTH_LIMIT){
//数组容量未满,使用数组存储
segmentArray[this.storeSize] = ds;
}else{
//数组容量已满,切换Map存储
//获取Map容器,如果Map未创建,则创建Map
Map<Character , DictSegment> segmentMap = getChildrenMap();
//将数组中的segment迁移到Map中
migrate(segmentArray , segmentMap);
//存储新的segment
segmentMap.put(keyChar, ds);
//释放当前的数组引用
this.childrenArray = null;
}
//segment数目+1
this.storeSize++;
}
}else{
//获取Map容器,如果Map未创建,则创建Map
Map<Character , DictSegment> segmentMap = getChildrenMap();
//搜索Map
ds = (DictSegment)segmentMap.get(keyChar);
if(ds == null){
//构造新的segment
ds = new DictSegment(keyChar);
segmentMap.put(keyChar , ds);
//当前节点存储segment数目+1
this.storeSize ++;
}
}
return ds;
}
****************************************************************
//判断某个词是否匹配
public Hit match(char[] charArray , int begin , int length){
Character keyChar = new Character(charArray[begin]);
DictSegment ds = null;
//引用实例变量为本地变量,避免查询时遇到更新的同步问题
DictSegment[] segmentArray = this.childrenArray;
Map<Character , DictSegment> segmentMap = this.childrenMap;
//STEP1 在节点中查找keyChar对应的DictSegment
if(segmentArray != null){
//在数组中查找
for(DictSegment seg : segmentArray){
if(seg != null && seg.nodeChar.equals(keyChar)){
//找到匹配的段
ds = seg;
}
}
}else if(segmentMap != null){
//在map中查找
ds = (DictSegment)segmentMap.get(keyChar);
}
//STEP2 找到DictSegment,判断词的匹配状态,是否继续递归,还是返回结果
if(ds != null){
if(length > 1){
//词未匹配完,继续往下搜索
return ds.match(charArray, begin + 1 , length - 1);
}else if (length == 1){//搜索最后一个char
Hit searchHit= new Hit();
if(ds.nodeState == 1){
//添加HIT状态为完全匹配
searchHit.setMatch();
}
if(ds.hasNextNode()){
//添加HIT状态为前缀匹配
searchHit.setPrefix();
}
return searchHit;
}
}
//STEP3 没有找到DictSegment, 将HIT设置为不匹配
return new Hit();
}
*********************************************************
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;
/**
* 判断是否完全匹配
*/
public boolean isMatch() {
return (this.hitState & MATCH) > 0;
}
public void setMatch() {
this.hitState = this.hitState | MATCH;
}
/**
* 判断是否是词的前缀
*/
public boolean isPrefix() {
return (this.hitState & PREFIX) > 0;
}
public void setPrefix() {
this.hitState = this.hitState | PREFIX;
}
/**
* 判断是否是不匹配
*/
public boolean isUnmatch() {
return this.hitState == UNMATCH ;
}
public void setUnmatch() {
this.hitState = UNMATCH;
}
}
二进制 int 状态
00 0 不匹配
01 1 匹配
10 2 是前缀。(ds.hasNextNode())
11 3 比配并且是前缀
*******************************************************
Lexeme 语义单元(词元)
//lexemeType常量
//普通词元
public static final int TYPE_CJK_NORMAL = 0;
//姓氏
public static final int TYPE_CJK_SN = 1;
//尾缀
public static final int TYPE_CJK_SF = 2;
//未知的
public static final int TYPE_CJK_UNKNOWN = 3;
//数词
public static final int TYPE_NUM = 10;
//量词
public static final int TYPE_NUMCOUNT = 11;
//英文
public static final int TYPE_LETTER = 20;
//词元的起始位移
private int offset;
//词元的相对起始位置
private int begin;
//词元的长度
private int length;
//词元文本
private String lexemeText;
//词元类型
private int lexemeType;
//当前词元的前一个词元
private Lexeme prev;
//当前词元的后一个词元
private Lexeme next;
**********************************************************************************
/**
* 分词器上下文状态
* @author 林良益
*
*/
public class Context{
//是否使用最大词长切分(粗粒度)
private boolean isMaxWordLength = false;
//记录Reader内已分析的字串总长度
//在分多段分析词元时,该变量累计当前的segmentBuff相对于reader的位移
private int buffOffset;
//最近一次读入的,可处理的字串长度
private int available;
//最近一次分析的字串长度
private int lastAnalyzed;
//当前缓冲区位置指针
private int cursor;
//字符窜读取缓冲
private char[] segmentBuff;
/*
* 记录正在使用buffer的分词器对象
* 如果set中存在有分词器对象,则buffer不能进行位移操作(处于locked状态)
*/
private Set<ISegmenter> buffLocker;
/*
* 词元结果集,为每次游标的移动,存储切分出来的词元
*/
private IKSortedLinkSet lexemeSet;
*******************************************************************************
private class IKSortedLinkSet{
//链表头
private Lexeme head;
//链表尾
private Lexeme tail;
//链表的实际大小
private int size;
/**
* 向链表集合添加词元 是一个有序的队列。排序方式。是//起始位置优先,再//词元长度优先
* @param lexeme
*/
private void addLexeme(Lexeme lexeme){。。}
/**
* 取出链表集合的第一个元素
* @return Lexeme
*/
private Lexeme pollFirst(){。。。}
/**
* 取出链表集合的最后一个元素
* @return Lexeme
*/
private Lexeme pollLast(){。。。}
/**
* 剔除集合汇总相邻的切完全包含的lexeme
* 进行最大切分的时候,过滤长度较小的交叠词元
*/
private void excludeOverlap(){。。。}
***********************************************************************************
分词器接口
public interface ISegmenter {
/**
* 从分析器读取下一个可能分解的词元对象
* @param segmentBuff 文本缓冲
* @param context 分词算法上下文
*/
void nextLexeme(char[] segmentBuff , Context context);
/**
* 重置子分析器状态
*/
void reset();
}
***************************************************************************************
/**
* IK Analyzer v3.2
* IK主分词器
* 注:IKSegmentation是一个lucene无关的通用分词器
* @author 林良益
*
*/
public final class IKSegmentation{
private Reader input;
//默认缓冲区大小
private static final int BUFF_SIZE = 3072;
//缓冲区耗尽的临界值
private static final int BUFF_EXHAUST_CRITICAL = 48;
//字符窜读取缓冲
private char[] segmentBuff;
//分词器上下文
private Context context;
//分词处理器列表
private List<ISegmenter> segmenters;
/**
* 获取下一个语义单元
* @return 没有更多的词元,则返回null
* @throws IOException
*/
public synchronized Lexeme next() throws IOException {。。。}
/**
* 取出词元集合中的下一个词元
* @return Lexeme
*/
private Lexeme buildLexeme(Lexeme lexeme){。。。}
***************************************************************************************
private class CSeg{
/*
* 词段开始位置
*/
private int start;
/*
* 词段的结束位置
*/
private int end;
}
public void reset() {
//重置已处理标识
doneIndex = -1;
_CSegList.clear();
}
}
**************************************************************************************
/**
* 中文词元处理子分词器,涵盖一下范围
* 1.中文词语
* 2.姓名
* 3.地名
* 4.未知词
* @author 林良益
*
*/
public class ChineseSegmenter implements ISegmenter {
/*
* 已完成处理的位置
*/
private int doneIndex;
/*
* 词段处理队列
*/
List<CSeg> _CSegList;
public void nextLexeme(char[] segmentBuff , Context context) {
//读取当前位置的char
char input = segmentBuff[context.getCursor()];
Hit hit = null;
if(CharacterHelper.isCJKCharacter(input)){//是中文(CJK)字符,则进行处理
if(_CSegList.size() > 0){
//处理词段队列
List<CSeg> tmpList = new ArrayList<CSeg>(_CSegList);
for(CSeg seg : tmpList){
hit = Dictionary.matchInMainDict(segmentBuff, seg.start, context.getCursor() - seg.start + 1);
if(hit.isMatch()){//匹配成词
//判断是否有不可识别的词段
if(seg.start > doneIndex + 1){
//输出并处理从doneIndex+1 到 seg.start - 1之间的未知词段
processUnknown(segmentBuff , context , doneIndex + 1 , seg.start - 1);
}
//输出当前的词
Lexeme newLexeme = new Lexeme(context.getBuffOffset() , seg.start , context.getCursor() - seg.start + 1 , Lexeme.TYPE_CJK_NORMAL);
context.addLexeme(newLexeme);
//更新goneIndex,标识已处理
if(doneIndex < context.getCursor()){
doneIndex = context.getCursor();
}
if(hit.isPrefix()){//同时也是前缀
//更新seg.end、seg.type,
seg.end = context.getCursor();
//seg.type = CSeg.TYPE_MATCH;
}else{ //后面不再可能有匹配了
//移出当前的CSeg
_CSegList.remove(seg);
}
}else if(hit.isPrefix()){//前缀,未匹配成词
//更新seg.end、seg.type
seg.end = context.getCursor();
//seg.type = CSeg.TYPE_PRE;
}else if(hit.isUnmatch()){//不匹配
//移出当前的CSeg
_CSegList.remove(seg);
}
}
}
//处理以input为开始的一个新CSeg
hit = Dictionary.matchInMainDict(segmentBuff, context.getCursor() , 1);
if(hit.isMatch()){//匹配成词
//判断是否有不可识别的词段
if(context.getCursor() > doneIndex + 1){
//输出并处理从doneIndex+1 到 context.getCursor()- 1之间的未知
processUnknown(segmentBuff , context , doneIndex + 1 , context.getCursor()- 1);
}
//输出当前的词
Lexeme newLexeme = new Lexeme(context.getBuffOffset() , context.getCursor() , 1 , Lexeme.TYPE_CJK_NORMAL);
context.addLexeme(newLexeme);
//更新doneIndex,标识已处理
if(doneIndex < context.getCursor()){
doneIndex = context.getCursor();
}
if(hit.isPrefix()){//同时也是前缀
//向词段队列增加新的词段
CSeg newCSeg = new CSeg();
newCSeg.start = context.getCursor();
newCSeg.end = newCSeg.start;
//newCSeg.type = CSeg.TYPE_MATCH;
_CSegList.add(newCSeg);
}
}else if(hit.isPrefix()){//前缀,未匹配成词
//向词段队列增加新的词段
CSeg newCSeg = new CSeg();
newCSeg.start = context.getCursor();
newCSeg.end = newCSeg.start;
//newCSeg.type = CSeg.TYPE_PRE;
_CSegList.add(newCSeg);
}else if(hit.isUnmatch()){//不匹配,当前的input不是词,也不是词前缀,将其视为分割性的字符
//输出从doneIndex到当前字符(含当前字符)之间的未知词
processUnknown(segmentBuff , context , doneIndex + 1 , context.getCursor());
//更新doneIndex,标识已处理
doneIndex = context.getCursor();
}
}else {//输入的不是中文(CJK)字符
if(_CSegList.size() > 0
&& doneIndex < context.getCursor() - 1){
for(CSeg seg : _CSegList){
//判断是否有不可识别的词段
if(doneIndex < seg.end){
//输出并处理从doneIndex+1 到 seg.end之间的未知词段
processUnknown(segmentBuff , context , doneIndex + 1 , seg.end);
}
}
}
//清空词段队列
_CSegList.clear();
//更新doneIndex,标识已处理
if(doneIndex < context.getCursor()){
doneIndex = context.getCursor();
}
}
//缓冲区结束临界处理
if(context.getCursor() == context.getAvailable() - 1){ //读取缓冲区结束的最后一个字符
if( _CSegList.size() > 0 //队列中还有未处理词段
&& doneIndex < context.getCursor()){//最后一个字符还未被输出过
for(CSeg seg : _CSegList){
//判断是否有不可识别的词段
if(doneIndex < seg.end ){
//输出并处理从doneIndex+1 到 seg.end之间的未知词段
processUnknown(segmentBuff , context , doneIndex + 1 , seg.end);
}
}
}
//清空词段队列
_CSegList.clear();
}
//判断是否锁定缓冲区
if(_CSegList.size() == 0){
context.unlockBuffer(this);
}else{
context.lockBuffer(this);
}
}
国家科研课题,看了你的博客,希望近期能联系到你,有机会沟通一下。如有兴趣,请联系我,zll_0105@sina.com