IKAnalyzer源码分析---2

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值