自定义Lucene的分词器Analyzer

0、先定义一下分词的结果

从最简单的做起,把输入的内容按一个字一个字进行切分。这里只是为了说明自定义的Analyzer以及Tokenizer如何写。Analyzer需要一个Tokenizer来进行切词,所以还需要定义一个自己的Tokenizer

1、先从Analyzer开始

Lucene版本为5.5以上

定义自己的Analyzer需要继承org.apache.lucene.analysis.Analyzer,并且实现TokenStreamComponents createComponents(String s)方法。咱们的Analyzer名字叫DemoAnalyzer。上代码:

import org.apache.lucene.analysis.Analyzer;

public class DemoAnalyzer extends Analyzer {
    @Override
    protected TokenStreamComponents createComponents(String s) {
        return new TokenStreamComponents(
                new DemoTokenizer()
        );
    }
}

createComponents方法里面直接new一个TokenStreamComponents对象,对象里面包含的是咱们自定义的DemoTokenizer对象。

2、定义Tokenizer

自定义的Tokenizer需要继承org.apache.lucene.analysis.Tokenizer,并且实现boolean incrementToken()方法。这个方法比较特殊:

  1. 此方法并不是一次性调用,而是进行的迭代调用,也就是说一次是读取一个分词,第二次再读取下一个分词,依次类推。

  2. 需要一系列的Attribute来存放对应的一个分词(也就是term)的信息,包括词本身、类型(type),位置,词的始末偏移量等等。这个是通过调用addAttribute方法实现的。

  3. input这个Reader并不需要自己传入,这个就是输入的需要分词的文本在父类已经实现了setReader(Reader input)方法,Lucene自己会调用此方法传入文本

上代码:

import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;

import java.io.IOException;

public class DemoTokenizer extends Tokenizer {

    private int position = 0;

    protected CharTermAttribute charAttr =
            addAttribute(CharTermAttribute.class);
    protected TypeAttribute typeAttr = addAttribute(TypeAttribute.class);
    private final PositionIncrementAttribute positionAttr =
            addAttribute(PositionIncrementAttribute.class);
    private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);

    @Override
    public boolean incrementToken() throws IOException {
        clearAttributes();

        char[] c = new char[1];

        int count = this.input.read(c, this.position, 1);

        // 返回false代表读完了
        if (count == -1) {
            return false;
        }

        charAttr.append(c[0]);
        typeAttr.setType("Char");
        positionAttr.setPositionIncrement(position+1);
        offsetAtt.setOffset(correctOffset(position), correctOffset(position+1));

        return true;
    }

    @Override
    public void reset() throws IOException {
        super.reset();
        this.position = 0;
    }
}

首先在类的内容需要几个存放Attribute的属性,比如CharTermAttribute charAttr,在调用incrementToken的时候需要先清空再append进charAttr

这里只是一个字一个字的进行切分,所以在incrementToken里面只需要读一个字符即可this.input.read(c, this.position, 1),然后append到charAttr里面。

这个方法的返回值需要注意,返回false表示已经读完的input里面的内容,后面没有文本了。返回true表示后面还有文本需要切分。

最后是reset方法,这里我们使用了position这个成员变量在存放每次读取下一个字符的开始位置,在Lucene调用reset的时候应该把position归零。

-1、Ok

这样就实现了自定义的Analyzer,如果想做其他方法的分词,就可以套用这个外壳了。