Solr的分词器在建索引和查询时使用,配置schema.xml文件时需要为字段的类型配置索引(index)和查询(query)时的分词器,一般索引和查询都会使用同一种分词器,确保按某种方式建立的索引也能以相同的分词方式检索出来。Solr支持自定义的分词器,只需要在schema.xml配置即可(基于Solr1.3版本)。
1. 自定义分词器实现原理
自定义的分词器需要继承自BaseTokenizerFactory或者已有的分词器,然后实现public TokenStream create(Reader input)方法。参照LetterTokenizerFactory的写法
public class LetterTokenizerFactory extends BaseTokenizerFactory {
public LetterTokenizer create(Reader input) {
return new LetterTokenizer(input);
}
}
LetterTokenizer就是一个按字母分词的分词器,分词器最重要的一个方法就是
public finalToken next(final Token reusableToken) throws IOException;
该方法负责如何把一个完整的字符串进行分词,每调用一下这个方法返回一个分词,所以如何分词,只需要把分词方式写在这个方法里即可。
LetterTokenizer没有直接实现分词方法,而是继承了CharTokenizer,CharTokenizer实现了通用字符分词方法,是个抽象类,子类可以通过实现protected abstractboolean isTokenChar(char c);和protectedchar normalize(char c)方法实现自己的分词方式。以下是CharTokenizer的next方法实现方式:
private int offset = 0, bufferIndex = 0, dataLen = 0;
private static final int MAX_WORD_LEN = 255;
private static final int IO_BUFFER_SIZE = 4096;
private final char[] ioBuffer = new char[IO_BUFFER_SIZE];
public final Token next(final Token reusableToken) throws IOException {
assert reusableToken != null;
reusableToken.clear();
int length = 0;
int start = bufferIndex;
char[] buffer = reusableToken.termBuffer();
while (true) {
if (bufferIndex >= dataLen) {
offset += dataLen;
dataLen = input.read(ioBuffer); //每次读入4096个字符
if (dataLen == -1) {
if (length > 0)
break;
else
return null;
}
bufferIndex = 0;
}
final char c = ioBuffer[bufferIndex++];//每次取一个字符出来判断
if (isTokenChar(c)) {//如果是分词可包含的字符则继续,不是则切断,并把遍历到的之前的字符当做一个分词返回,所以子类只要实现自己的isTokenChar(c)方法就可以实现按自己的方式分词, LetterTokenizer的实现方式是如果是字母才返回真,所以hello world会被分为hello和world,因为遇到空格时isTokenChar返回假就会把之前的字符当做一个分词返回。
if (length == 0) // start of token
start = offset + bufferIndex - 1;
else if (length == buffer.length)
buffer =reusableToken.resizeTermBuffer(1+length);
buffer[length++] = normalize(c);//可以对分词进行处理,比如LowCaseTokenizer就是通过实现normalize(c),在方法中把每个字母转成小写实现的。
if (length == MAX_WORD_LEN) // buffer overflow!
break;
} else if (length > 0) // at non-Letter w/ chars
break; // return 'em
}
reusableToken.setTermLength(length);
reusableToken.setStartOffset(start);
reusableToken.setEndOffset(start+length);
return reusableToken;
}
2. 可以按拼音首字母检索的分词器实现
a) 把汉字转化为首字母
这里使用了常用的方式,google一下很多,ConvertToPY.getFirstLetter(char c)工具类实现把汉字转化为首字母。
b) 如何把首字母加入到分词中
要让用户通过首字母检索到内容,那么就必须在建立索引时为汉字的拼音首字母也要建索引,这样才能按首字母搜索,通过分析以上代码,可以发现LetterTokenizerFactory类中只有一个方法,该方法传进一个input也就是未分词的完整字符串,我们只需要在他返回之前对这个input加工处理一下就可以了。
继续贴代码:
public class LetterTokenizerFactory extendsBaseTokenizerFactory {
public LetterTokenizer create(Readerinput) {
return new LetterTokenizer(input);
}
}
在方法中添加了拼音首字母的代码如下:
public LetterTokenizer create(Reader input) {
LetterTokenizer ts = new LetterTokenizer(input);//先拿到原始的字符串
Token reusableToken = new Token();
StringBuilder sbPrefix = new StringBuilder();
StringBuilder sbSuffix = new StringBuilder();
try {
while (ts.next(reusableToken) != null) {
//取得每个分词,并得到其首字母保存起来,最后追加到整个汉字字符串的最后
sbPrefix.append(reusableToken.term());
String ch = ConvertToPY.getFirstLetter(reusableToken.term()
.charAt(0));
if (!ch.equals("")) {
sbSuffix.append(
ConvertToPY.getFirstLetter(reusableToken.term()
.charAt(0)));
}
}
} catch (IOException e) {
e.printStackTrace();
}
//最后把拼好的带汉字和首字母的字符串封装成一个reader返回。
return new LetterTokenizer(new StringReader(sbPrefix.toString()
+ sbSuffix.toString()));
}
这里把汉字的首字母组拼后追加到汉字的最后,这样就把首字母加入到了待分词的字符串中,接下来再通过分词器就可以把组拼好的带拼音首字母的字符串进行分词。如:韦小宝—>韦小宝wxb
c) 如何进行分词
因为solr和Lucene本身都没有提供对单个字符进行分词的分词器,而我们的需求是把内容按单个字符分词,这样用户输入一个字符就能检索出内容,StandardTokenizerFactory可以把中文按照一个字一个字拆分,但是对英文不行,虽然我们也可以把拼音首字母用分割符去组拼,这样StandardTokenizerFactory也能分出来,但总觉得还不是太好。那么怎么去实现按单字符分词的分词器呢,如果阅读过以上CharTokenizer的next方法,并且理解的话,问题就迎刃而解。CharTokenizer通过isTokenChar方法判断是否需要进行分词,对于我们的需求就是不要进行判断,而是每次读到一个字符都进行分词,这样就实现了。官方为什么不提供这样的分词器,我认为是他们觉得意义不大,因为拆分成单个字符毕竟会影响效率。以下是按单个字符分词的next方法,基本就是从CharTokenizer的next方法注释掉几行而来的。
public Token next(Token reusableToken) throws IOException {
reusableToken.clear();
int length = 0;
int start = bufferIndex;
char[] buffer =reusableToken.termBuffer();
// while (true) {
if (bufferIndex >= dataLen) {
offset += dataLen;
dataLen = input.read(ioBuffer);
if (dataLen == -1) {
return null;
}
bufferIndex = 0;
}
final char c = ioBuffer[bufferIndex++];
// if (isTokenChar(c)) { // if it's a token char
if (length == 0) // start of token
start = offset + bufferIndex - 1;
else if (length == buffer.length)
buffer =reusableToken.resizeTermBuffer(1+length);
buffer[length++] =normalize(c); // buffer it, normalized
// if (length == 1) // buffer overflow!
// break;
// } else if (length >0) // at non-Letter w/ chars
// break; // return 'em
//}
reusableToken.setTermLength(length);
reusableToken.setStartOffset(start);
reusableToken.setEndOffset(start+length);
return reusableToken;
}
d) 如何使用
把写好的分词器打成jar包,放到可以在solrconfig.xml配置路劲的lib包中,然后在schema.xml中就可以使用自己的分词器了,如
<fieldType name="text"class="solr.TextField" >
<analyzer>
<tokenizerclass=" com.ckh.solr.analysis.PYOneCharTokenizerFactory "/>
<filterclass="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
PYOneCharTokenizerFactory是自己写的能够按拼音首字母搜索的分词器。
Solr还支持对已经分出来的分词进行一级级过滤,只需要配置filter即可。