Solr TokenFilter开发详解

以来自http://blog.csdn.net/itbasketplayer/article/details/40742117的NumberEnglishFilter为例子做剖析。NumberEnglishFilter主要是为了在索引时切分数字+字母,比如"2014show","2014"、"show"作为term,这样检索2014或者show都能检索出来。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NumberEnglishFilter extends TokenFilter {
    private static final Logger logger = LoggerFactory.getLogger(NumberEnglishFilter.class);
    private char[] curTermBuffer;
    /* char[]总长度 */
    private int curTermLength;
    /* 当前切割处,+1表示下一次切割处 */
    private int curGramSize;
    /* number和english位置 */
    private List < Integer > positions;
    /* 相对位置,即在整个输入词的位置 */
    private int tokStart;
    /* positions所在的位置 */
    private int position;
    private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
    /* 表示开始位置,结束位置 */
    private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
    /* private final TypeAttribute typeAtt = addAttribute(TypeAttribute.class); */
    public NumberEnglishFilter(Version matchVersion, TokenStream input) {
        super(input);
    }@Override public boolean incrementToken() throws IOException {
        while (true) {
            /* 表示上一个token结束,进入下一个token */
            if (curTermBuffer == null) {
                if (!input.incrementToken()) {
                    return false;
                } else {
                    curTermBuffer = termAtt.buffer().clone();
                    curTermLength = termAtt.length();
                    /* 放置position list */
                    positions = getPositions(curTermBuffer, curTermLength);
                    tokStart = offsetAtt.startOffset();
                    curGramSize = positions.get(0);
                    position = 0;
                }
            }
            if (curGramSize < curTermLength && curTermLength > 1 && positions.size() > 2) {
                try {
                    position++;
                    offsetAtt.setOffset(tokStart + curGramSize, tokStart + positions.get(position));
                    termAtt.copyBuffer(curTermBuffer, curGramSize, positions.get(position) - curGramSize);
                    curGramSize = positions.get(position);
                    return true;
                } catch(Exception e) {
                    logger.error(position + "\t" + new String(curTermBuffer));
                    clearAttributes();
                    offsetAtt.setOffset(tokStart + 0, tokStart + curTermLength);
                    termAtt.copyBuffer(curTermBuffer, 0, curTermLength);
                    curTermBuffer = null;
                    return true;
                }
            } else {
                clearAttributes();
                offsetAtt.setOffset(tokStart + 0, tokStart + curTermLength);
                termAtt.copyBuffer(curTermBuffer, 0, curTermLength);
                curTermBuffer = null;
                return true;
            }
        }
    }
    /* 记录下number和english位置 */
    public List < Integer > getPositions(char[] term, int length) {
        List < Integer > list = new ArrayList < Integer > ();
        list.add(0);
        for (int i = 0; i < length;) {
            if (Character.isDigit(term[i])) {
                while (++i < length && Character.isDigit(term[i]));
                list.add(i);
                continue;
            } else if (i < length && Character.isLetter(term[i])) {
                while (++i < length && Character.isLetter(term[i]));
                list.add(i);
                continue;
            } else {
                list.add(++i);
            }
        }
        if (!Character.isDigit(term[length - 1]) && !Character.isLetter(term[length - 1])) list.add(length);
        return list;
    }
}

代码中,curTermBuffer用来存储上层传下来的每一个token(token初始存在termAtt中),当curTermBuffer为空时,说明上一个token已经处理完毕,因此需要通过input.incrementToken()从Token Stream里面获取一个新的token,存在curTermBuffer中,然后处理该token(curTermBuffer)得到token中数字和字母之间的切割点位置,存在positions数组中。接下来调用incrementToken()函数,根据positions数组,将token中的一个数字串或者一个字母串切割出来,存到termAtt中,然后返回true告诉上层继续调用incrementToken()函数;新一次调用中,代码检测到curTermBuffer非空,则直接根据positions数组,再次将token中的一个数字串或者一个字母串切割出来存到termAtt中,返回true让上层继续调用incrementToken()函数。一直重复这个过程直到curTermBuffer切割完毕,将最后一个数字串或者字母串存到termAtt中,然后将curTermBuffer置为空、返回true,好让下一次调用时代码去处理新的token(之前的每次调用都在处理上一个token)。 
注意: 
1.input.incrementToken()返回0表示Token Stream里面已经没有token,因此重写的incrementToken()函数直接就返回false告诉上层整个Token Stream处理完毕;返回true则表示当前还有token在处理,incrementToken()函数需要继续被上层调用。 
2.每一次调用incrementToken()函数处理的是curTermBuffer,不等于每次处理的是新的token(处理完一个token可能需要多次函数的调用),处理的是否是新的token要看curTermBuffer有没有在该次调用中去获取新的token。 
3.每一次调用incrementToken()函数,如果不返回false,则都会从当前token(存在curTermBuffer中)中处理出一个结果存在termAtt中、然后返回true,上层代码通过调用incrementToken()函数可以知道需不需要继续调用、并从termAtt中接收上一次调用的结果。可以说termAtt在solr tokenfilter处理一个token的过程中起到承上启下的作用:首先,需要处理的token先传到termAtt中,然后暂存到curTermBuffer;之后,原始的token内容就一直存在curTermBuffer中直到该该token处理完毕,而termAtt在该token处理的过程中起到输出结果的作用,每一次从token中处理出一个结果,就存在termAtt中,然后由上层接收(根据具体需求,一个token可能需要多次调用处理出多个结果,比如上述代码,一个token就需要处理出多个数字串、字母串,处理过程中每一次得到一个结果都存到termAtt中;注意,一次调用只允许你从token中处理出一个你想要的结果,如果你想从一个token中获得一组结果,那需要多次调用)。每一个token的处理过程中,termAtt中都依次扮演上述两个角色。 
4.在Solr Tokenizer开发接口不熟悉的情况下,采用Solr自带的tokenizer——solr.KeywordTokenizerFactory(该tokenizer不会对solr doc的字段做任何处理,会将字段内容当作一个token传给后续的TokenFilter来处理),结合TokenFilter开发可以将tokenizer的功能也在tokenfilter里面完成(不过这种做法应该不大好)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值