文章目录
简介
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



被折叠的 条评论
为什么被折叠?



