《Lucene in Action 第二版》第4章节 学习总结 -- Lucene中的分析
通过第四章的学习,可以了解lucene的分析过程是怎样的,并且可以学会如何使用lucene内置分析器,以及自定义分析器。下面是具体总结
1. 分析(Analysis)是什么?
在lucene中,分析就是指:将域(Field)文本转换成最基本的索引表示单元---项(Term)的过程。而项(Term)又是由语汇单元(Token)以及它所属的域名组合而成的。
在索引过程中存在分析(IndexWriter的初始化中需要放入一个Analyzer的实例;并且如果要使Analyzer生效,则需要使用Index.ANALYZED或者Index.ANALYZED_NO_NORMS属性);在利用QueryParser进行搜索的过程中也存在分析(QueryParser的初始化中也需要放入一个Analyzer的实例)。
分析是通过分析器(Analyzer)进行的。Lucene推荐,在索引和搜索期间,使用相同的Analyzer进行处理。
分析器不能做什么?分析器的前提是域已经分好了。因此必须在使用分析器之前,先把文本预先解析并形成域。分析器内不可以创建新的域。
2. Analyzer的作用,就是分析输入的字符文件。其主要的过程如下:
通过调用某个具体Analyzer子类的TokenStream(String fieldName, Reader reader)(reader里面就包含了输入的要解析的文本,fieldname可以为空,即"";但是在不为空时,它起到什么作用呢?),将输入的文本分析成Term。而在TokenStream内部,则可以:1.首先用一个具体的Tokennzer对象从输入的文本创建初始语汇单元序列;2.然后用任意数量(可以是0个)的tokenFilter对象来修改这些语汇单元。最终产生的就是语汇单元流:TokenStream。
3. 语汇单元流是由各个语汇单元组成的。而每个语汇单元的组成,包括了:文本值本身+其他一些元数据。这些都可以理解为该语汇单元的属性,它们是可以通过具体对象实例的API方法得到的。具体为:
TermAttribute ---- 语汇单元对应的文本,通过 .term()方法就可以得到具体文本值
PositionIncrementAttribute --- 本语汇单元相对于前一个语汇单元的位置增量(默认值是1)。通过. getPositionIncrement()可以得到位置增量的值。
OffsetAttriute --- 语汇单元的起始字符和终止字符的偏移量。其中起始字符偏移量表示的是语汇单元的文本的起始字符在原始文本中的位置,通过.startOffset()得到;而终止字符偏移量则表示语汇单元的文本终止字符的下一个位置。通过.endOffset()得到
TypeAttribute --- 语汇单元类型(默认是 word)。通过.type()得到。
FlagsAttribute --- 自定义标志位 (具体怎么用,尚不清楚)
PayloadAttribute --- 每个语汇单元的byte[]类型有效负载(具体怎么用,尚不清楚)
4. Lucene的内置分析器是需要熟练掌握和运用的。而且书上说,适用范围最广的分析器是StandardAnalyzer
5. 如何创建自己的分析器:一般构建自定义分析器,需要定义Analyzer子类以及自定义TokenFilter;而Tokenizer一般是用现成的。常规套路如下:
A. 自定义一个Analyzer,继承自Analyzer,即书上的例子:
public class MetaphoneReplacementAnalyzer extends Analyzer {
@Override
public TokenStream tokenStream(String fieldName, Reader reader) {
// TODO Auto-generated method stub
returnnew MetaphoneReplacementFilter(new LetterTokenizer(reader));
}
}
B. 自定义TokenFilter(可以是一个或者多个)。在自定义的TokenFilter中,一般就要利用语汇单元的各种属性来对语汇单元进行过滤了。并且还要实现在TokenFilter中唯一要实现的方法:incrementToken()
public class MetaphoneReplacementFilter extends TokenFilter {
public static final String METAPHONE = "metaphone";
private Metaphone metaphoner = new Metaphone();
private TermAttribute termAttr;
private TypeAttribute typeAttr;
protected MetaphoneReplacementFilter(TokenStream input) {
super(input);
// TODO Auto-generated constructor stub
termAttr = addAttribute(TermAttribute.class);
typeAttr = addAttribute(TypeAttribute.class);
}
@Override
public boolean incrementToken() throws IOException {
// TODO Auto-generated method stub
if(!input.incrementToken()) {
return false;
}
String encoded;
encoded = metaphoner.encode(termAttr.term());
termAttr.setTermBuffer(encoded);
typeAttr.setType(METAPHONE);
return true;
}
}
6. 同时,要知道:网络上有很多现成的分析器实现,可以借用的
7. 好东西:PerFieldAnalyzerWrapper
经常的,我们会遇到这样的索引:一个Doc中有多个Field,每个Field中存储的值是不一样的。比如:一个域:partnum,存储的值是"Q36",存储的方式是Store,但是NOTANALYZED(也就是要求将Q36作为一个整体进行索引);另一个域:description,存储的值是"Illidium Space Modulator"",存储的方式是Store,同时是ANALYZED。然后,我们需要利用QueryParser来针对字符串"partnum:Q36 AND SPACE"进行搜索。
这时,就会遇到一个问题:用哪种Analyzer呢?因为QueryParser的构造函数中,只能放入一个Analyzer,而这里,partnum域是NOTANALYZED,而description域是ANALYZED。似乎Lucene自带的Analyzer是无法同时满足这两个域的搜索要求的。那么,这里的PerFieldAnalyzerWrapper就可以帮忙了。它可以在默认Analyzer的基础上,针对特定域可以绑定一个特定的Analyzer。然后,我们将PerFiledAnalyzerWrapper所创建的这个“混合”分析器,放入QueryParser的构造方法中就可以了。具体例子代码如下:
PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(new SimpleAnalyzer()); //先构建一个PerFieldAnalyzerWrapper实例,放入缺省的分析器
analyzer.addAnalyzer("partnum", new KeywordAnalyzer()); //给特定Field绑定特定Analyzer
Query query = new QueryParser(Version.LUCENE_30, "description", analyzer).parse("partnum:Q36 AND SPACE"); //这里就要采用PerFieldAnalyzerWrapper分析器了