Lucene分析器的实现。
Lucene(分词)过滤器TokenFilter类,以及继承它的子类的实现类。
TokenFilter类的继承关系,如图所示:
TokenFilter是一个抽象类,定义了对一个经过分词(Tokenizer)后的TokenStream进行过滤的功能,它的源代码如下所示:
package org.apache.lucene.analysis;
import java.io.IOException;
public abstract class TokenFilter extends TokenStream {// 通过输入一个TokenStreamprotected TokenStream input;
protected TokenFilter(TokenStream input) { this.input = input;}
public void close() throws IOException { input.close();}}
ChineseFilter中文过滤器类:
package org.apache.lucene.analysis.cn;
import java.util.Hashtable;import org.apache.lucene.analysis.*;
// 中文过滤器,包含了含有外文字符的情况,因为中文搜索的关键字可能是:中文+外文
public final class ChineseFilter extends TokenFilter {
// 这里给出了英文的终止词汇,如果直接调用根本对中文没有过滤作用,或者也可以在此添加终止词汇,比如:“也、了、啊、吧、呵……”等等一些助词叹词,因为这些词语对于检索没有意义 public static final String[] STOP_WORDS = { "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with" };
private Hashtable stopTable;
// 构造函数,初始化一个终止词汇表 (过滤掉的词汇)
public ChineseFilter(TokenStream in) { super(in);
stopTable = new Hashtable(STOP_WORDS.length); for (int i = 0; i < STOP_WORDS.length; i++) stopTable.put(STOP_WORDS[i], STOP_WORDS[i]); }
// 如果stopTable为空,说明该过滤类根本没有进行过滤,直接将读入的词条(Token)返回
public final Token next() throws java.io.IOException {
for (Token token = input.next(); token != null; token = input.next()) { String text = token.termText(); // 获取分词后词条的内容
// 这里,如果chineseChar是单个字,则Character.getType(chineseChar)返回的类型值为5;大写英文字符类型值为1,小写字符类型值为2 if (stopTable.get(text) == null) { // 如果分词后的词条内容没有出现在过滤列表stopTable中 switch (Character.getType(text.charAt(0))) {
case Character.LOWERCASE_LETTER: case Character.UPPERCASE_LETTER:
// 如果英文词条不是单个字符 if (text.length()>1) { return token; } break; case Character.OTHER_LETTER:
// 如果中文的过滤词汇表中是单个字的形式,则若是一个词汇中有一个字出现在stopTable中,那么整个词条都被过滤掉了。这是不合理的;所以只要把stopTable设置为空就可以实现,即不对分词的词条进行过滤 // 也可以在这里添加对词条的限制,来实现过滤
return token; }
}
} return null; }
}
再看一个限制词条长度的过滤器类:
package org.apache.lucene.analysis;
import java.io.IOException;
// 词条过长或者过短,则过滤掉public final class LengthFilter extends TokenFilter {
final int min;final int max;
// 构造函数,初始化此条长度上限和下限public LengthFilter(TokenStream in, int min, int max){ super(in); this.min = min; this.max = max;}
// 返回长度在min与max之间的词条public final Token next() throws IOException{ for (Token token = input.next(); token != null; token = input.next()) { int len = token.termText().length(); if (len >= min && len <= max) { return token; } // note: else we ignore it but should we index each part of it? }
return null;}}
可见,过滤器的目的就是对分词获得的词条的一些属性信息进行过滤,原则是:这些属性信息对检索没有实际意义。通过阅读标准过滤器类,再综合各种需要,我们就能根据自己的需要实现词条的过滤。Lucene包里给出的标准过滤器类。其实是对英文字符过滤的的,源代码如下:
package org.apache.lucene.analysis.standard;
import org.apache.lucene.analysis.*;
public final class StandardFilter extends TokenFilterimplements StandardTokenizerConstants {
public StandardFilter(TokenStream in) { super(in);}
// APOSTROPHE、ACRONYM等都是在一个常量接口类StandardTokenizerConstants里定义的常量,分别代表在过滤中可能要对其进行处理的字符
private static final String APOSTROPHE_TYPE = tokenImage[APOSTROPHE];private static final String ACRONYM_TYPE = tokenImage[ACRONYM];// 如果词条不空,进行处理后返回经过过滤处理后的词条public final org.apache.lucene.analysis.Token next() throws java.io.IOException { org.apache.lucene.analysis.Token t = input.next();
if (t == null) return null;
String text = t.termText(); String type = t.type();
// 英文中的 's或'S对检索意义不大,可以删除掉 if (type == APOSTROPHE_TYPE && (text.endsWith("'s") || text.endsWith("'S"))) { return new org.apache.lucene.analysis.Token(text.substring(0,text.length()-2),t.startOffset(), t.endOffset(), type);
} else if (type == ACRONYM_TYPE) { // 对“圆点”进行处理,直接把出现的“圆点”删除掉,保留其它字符 StringBuffer trimmed = new StringBuffer(); for (int i = 0; i < text.length(); i++) {char c = text.charAt(i);if (c != '.') trimmed.append(c); } return new org.apache.lucene.analysis.Token(trimmed.toString(), t.startOffset(), t.endOffset(), type);
} else { return t; }}}
常量接口类StandardTokenizerConstants 定义如下:
package org.apache.lucene.analysis.standard;
public interface StandardTokenizerConstants {
int EOF = 0;int ALPHANUM = 1;int APOSTROPHE = 2;int ACRONYM = 3;int COMPANY = 4;int EMAIL = 5;int HOST = 6;int NUM = 7;int P = 8;int HAS_DIGIT = 9;int ALPHA = 10;int LETTER = 11;int CJ = 12;int KOREAN = 13;int DIGIT = 14;int NOISE = 15;
int DEFAULT = 0;
String[] tokenImage = { "<EOF>", "<ALPHANUM>", "<APOSTROPHE>", "<ACRONYM>", "<COMPANY>", "<EMAIL>", "<HOST>", "<NUM>", "<P>", "<HAS_DIGIT>", "<ALPHA>", "<LETTER>", "<CJ>", "<KOREAN>", "<DIGIT>", "<NOISE>",};}
还有一个org.apache.lucene.analysis.StopFilter类,和ChineseFilter类的实现很相似,只是这里把过滤字符列表是初始化一个StopFilter过滤器的时候指定的,而且该类实现了对过滤字符类表中字符进行转换的功能。
我感觉,最应该好好研究的是关于同义词的过滤问题。Lucene包中给了一个org.apache.lucene.index.memory.SynonymTokenFilter过滤类,比较复杂,因为这里面涉及到了一个重要的类:org.apache.lucene.index.memory.SynonymMap。通过研究对英文中同义词的过滤,来考虑中文同义词过滤的问题。
