IKAnalyzer源码分析---2

来源:互联网 发布:全球实时网络攻击 编辑:程序博客网 时间:2024/06/05 22:58

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列表中。

0 0