IKAnalyzer源码分析—incrementToken
上一章分析了IKAnalyzer的初始化,本章开始分析incrementToken函数,该函数是IKAnalyzer分词的主要函数。
TokenStream::incrementToken
public boolean incrementToken() throws IOException {
clearAttributes();
Lexeme nextLexeme = _IKImplement.next();
if(nextLexeme != null){
termAtt.append(nextLexeme.getLexemeText());
termAtt.setLength(nextLexeme.getLength());
offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
endPosition = nextLexeme.getEndPosition();
typeAtt.setType(nextLexeme.getLexemeTypeString());
return true;
}
return false;
}
incrementToken函数一次获得一个词元,clearAttributes函数用来初始化参数,_IKImplement的next函数获取下一个词元nextLexeme,该函数也是incrementToken的核心函数,获得nextLexeme后,就将该词元的各个属性添加到lucene的Attribute结构中,termAtt用于保存词元,offsetAtt保存位置信息,typeAtt保存词元类型。
TokenStream::incrementToken->IKSegmentation::next
public synchronized Lexeme next()throws IOException{
Lexeme l = null;
while((l = context.getNextLexeme()) == null ){
int available = context.fillBuffer(this.input);
if(available <= 0){
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());
context.outputToResult();
context.markBufferOffset();
}
return l;
}
为了提高效率,next函数一次处理多个词元,然后将其保存在AnalyzeContext的缓存results列表中,一次获得一个词元。
首先通过getNextLexeme函数判断AnalyzeContext的缓存里是否有处理过的词元,如果有就直接返回该Lexeme,如果没有,首先调用fillBuffer函数将输入input填充到缓存里segmentBuff,如果没有读取到新的数据,并且上一次的所有数据都已经处理完毕,则直接返回,如果读取到新的数据,则调用initCursor初始化,然后遍历segmenters列表,并调用每个Segmenter的analyze函数进行处理,根据上一章的分析可知,这里会依次取出LetterSegmenter、CN_QuantifierSegmenter、CJKSegmenter进行处理。如果剩余的数据不足,或者读取完整个segmentBuff缓存,则跳出循环,否则通过moveCursor函数移动指针,读取下一个数据。
循环读取后,就通过reset函数重新初始化前面的三个Segmenter。然后调用IKArbitrator的process函数进行歧义处理。处理后,再通过AnalyzeContext的outputToResult函数将最终的输出结果保存在AnalyzeContext的result中,后续就可以直接通过getNextLexeme函数获取到了。
TokenStream::incrementToken->IKSegmentation::next->AnalyzeContext::getNextLexeme
Lexeme getNextLexeme(){
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{
result.setLexemeText(String.valueOf(segmentBuff , result.getBegin() , result.getLength()));
break;
}
}
return result;
}
成员变量results为Lexeme列表,Lexeme封装了解析玩的词元信息,首先从results中获取一个结果Lexeme,然后通过compound函数判断是否需要和下一个词元进行合并。如果该Lexeme对应的词元在停词列表中,则通过pollFirst继续获取下一个Lexeme,否则从成员变量segmentBuff(封装了文章对应的char数组)获取该词元文本并返回。
TokenStream::incrementToken->IKSegmentation::next->AnalyzeContext::getNextLexeme->compound
private void compound(Lexeme result){
if(Lexeme.TYPE_ARABIC == result.getLexemeType()){
Lexeme nextLexeme = this.results.peekFirst();
boolean appendOk = false;
if(Lexeme.TYPE_CNUM == nextLexeme.getLexemeType()){
appendOk = result.append(nextLexeme, Lexeme.TYPE_CNUM);
}else if(Lexeme.TYPE_COUNT == nextLexeme.getLexemeType()){
appendOk = result.append(nextLexeme, Lexeme.TYPE_CQUAN);
}
if(appendOk){
this.results.pollFirst();
}
}
if(Lexeme.TYPE_CNUM == result.getLexemeType() && !this.results.isEmpty()){
Lexeme nextLexeme = this.results.peekFirst();
boolean appendOk = false;
if(Lexeme.TYPE_COUNT == nextLexeme.getLexemeType()){
appendOk = result.append(nextLexeme, Lexeme.TYPE_CQUAN);
}
if(appendOk){
this.results.pollFirst();
}
}
}
如果当前词元类型是数字(TYPE_ARABIC),并且下一个词元类型是中文数字(TYPE_CNUM),则将这两个词元合并并设置类型为中文数字,如果下一个词元类型时量词(TYPE_COUNT),则将这两个词元合并并设置类型为中文数量词(TYPE_CQUAN)。
如果第一次处理后的当前词元是中文数字,并且下一个词元类型时量词(TYPE_COUNT),则将这两个词元合并并设置类型为中文数量词(TYPE_CQUAN)。
TokenStream::incrementToken->IKSegmentation::next->fillBuffer
int fillBuffer(Reader reader) throws IOException{
int readCount = 0;
if(this.buffOffset == 0){
readCount = reader.read(segmentBuff);
}else{
int offset = this.available - this.cursor;
if(offset > 0){
System.arraycopy(this.segmentBuff , this.cursor , this.segmentBuff , 0 , offset);
readCount = offset;
}
readCount += reader.read(this.segmentBuff , offset , BUFF_SIZE - offset);
}
this.available = readCount;
this.cursor = 0;
return readCount;
}
fillBuffer函数读取input数据到缓存segmentBuff中,假设不是第一次读取,则需要将上一次未处理完的数据一起处理,最后返回一共读取到的数据。
TokenStream::incrementToken->IKSegmentation::next->AnalyzeContext::needRefillBuffer
boolean needRefillBuffer(){
return this.available == BUFF_SIZE
&& this.cursor < this.available - 1
&& this.cursor > this.available - BUFF_EXHAUST_CRITICAL
&& !this.isBufferLocked();
}
needRefillBuffer用来判断是否接近读取完segmentBuff缓存,判断条件是剩余的未读取的数据是否小于BUFF_EXHAUST_CRITICAL,默认值为100。
TokenStream::incrementToken->IKSegmentation::next->AnalyzeContext::outputToResult
void outputToResult(){
int index = 0;
for( ; index <= this.cursor ;){
if(CharacterUtil.CHAR_USELESS == this.charTypes[index]){
index++;
continue;
}
LexemePath path = this.pathMap.get(index);
if(path != null){
Lexeme l = path.pollFirst();
while(l != null){
this.results.add(l);
index = l.getBegin() + l.getLength();
l = path.pollFirst();
if(l != null){
for(;index < l.getBegin();index++){
this.outputSingleCJK(index);
}
}
}
}else{
this.outputSingleCJK(index);
index++;
}
}
this.pathMap.clear();
}
private void outputSingleCJK(int index){
if(CharacterUtil.CHAR_CHINESE == this.charTypes[index]){
Lexeme singleCharLexeme = new Lexeme(this.buffOffset , index , 1 , Lexeme.TYPE_CNCHAR);
this.results.add(singleCharLexeme);
}else if(CharacterUtil.CHAR_OTHER_CJK == this.charTypes[index]){
Lexeme singleCharLexeme = new Lexeme(this.buffOffset , index , 1 , Lexeme.TYPE_OTHER_CJK);
this.results.add(singleCharLexeme);
}
}
outputToResult遍历当前缓冲至当前指针cursor位置,如果某个char的类型为CHAR_USELESS,则直接忽略,否则从pathMap中获取LexemePath,LexemePath保存了经过歧义处理后的多个词元,如果LexemePath不存在,则直接通过outputSingleCJK单字输出,如果存在,则将其中的多个Lexeme添加到最终的results列表中,并且对该Lexeme至下一个Lexeme之间的缓存进行单字输出。
outputSingleCJK函数检查待输出的单字类型是否为CHAR_CHINESE或者CHAR_OTHER_CJK,然后将单字封装为Lexeme并添加到results列表中。