Lucene 3 中写自己的分词器Analyzer

转:http://blog.sina.com.cn/s/blog_4b3b7aff0100g3wh.html

Lucene的分词器部分是经常被修改的,我们实验室自己的分词器更适合自然语言处理,因此如何挂载自己的分词结果呢?在Lucene 3 中,发生了较大的变化。研究了半天,只需重写Tokenizer即可,关键是incrementToken()函数,用来向索引表写入词语数据和位移数据。其中的FMM.fmm()采用java本地接口jni调用dll实现,dll采用C写成,分词效率极高,且具有很强的未登录词识别功能;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TermAttribute;
import org.apache.lucene.util.AttributeSource;
public class ICTSegTokenizer extends Tokenizer {
 public ICTSegTokenizer(Reader in) {
  super(in);
  init();
 }
 public ICTSegTokenizer(AttributeSource source, Reader in) {
  super(source, in);
  init();
 }
 public ICTSegTokenizer(AttributeFactory factory, Reader in) {
  super(factory, in);
  init();
 }
 
 private void init() {
  termAtt = (TermAttribute) addAttribute(TermAttribute.class);
  offsetAtt = (OffsetAttribute) addAttribute(OffsetAttribute.class);
 }
 
 public void seg() {
  try {
   BufferedReader buffer = new BufferedReader(input);// input转缓存
   StringBuffer sb = new StringBuffer();// 存放每一行内容
   String line = "";
   while ((line = buffer.readLine()) != null) {
    sb.append(line);
   }
   String result = FMM.fmm(sb.toString(), "\t");
   // System.out.println(sb.toString() + ":" + result);
   FMM.freeMem();
   resultArray = result.split("\t");
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 private int index = 0, len = 0, offset = 0;
 private String[] resultArray;
 private TermAttribute termAtt;
 private OffsetAttribute offsetAtt;
 
 public boolean incrementToken() {
  clearAttributes();
  if (index == 0) {
   seg();
   index = 0;
   len = resultArray.length;
  }
  if (index > len - 1) {// 超过分词结果时退出
   index = 0;
   len = 0;
   return false;
  }
  String word = resultArray[index];// 得到该索引的词
  termAtt.setTermBuffer(word);// 设置termAttr
  int wordLen = word.length();// 词的长度
  offsetAtt.setOffset(offset, offset + wordLen);// 设置位移
  // System.out.println(word + ":(" + Integer.toString(offset) + "-"
  // + Integer.toString(offset + wordLen) + ")");
  offset += wordLen;
  index++;
  return true;
 }
 public final void end() {
  // set final offset
  final int finalOffset = offset;
  this.offsetAtt.setOffset(finalOffset, finalOffset);
 }
 public void reset() throws IOException {
  super.reset();
  offset = index = 0;
 }
 public void reset(Reader input) throws IOException {
  super.reset(input);
  reset();
 }
}

【转】打造自己的中文分词器之如何让Lucene认识自己的分词器

转: http://hi.baidu.com/k_boy/item/9b7172106ff913fc746a84f1

Lucene允许分词器的扩充,或换句话说也就是允许你自己编写的分词器应用到Lucene中,那么Lucene是如何做到这点的呢?如果让我们来自己设计,我们会如何做呢?下面将以Lucene自带的标准分词器StandardAnalyzer来予以说明
首先看一下StandardAnalyzer的代码,为了显示简洁,以及突出重点,将不显示StandardAnalyzer的所有代码,先来看:

public class StandardAnalyzer extends Analyzer {
 public TokenStream tokenStream(String fieldName, Reader reader){
 TokenStream result = new StandardTokenizer(reader);
 result = new StandardFilter(result);
 result = new LowerCaseFilter(result);
 result = new StopFilter(result, stopSet);
 return result;
 }
}

可以看到StandardAnalyzer继承了一个Analyzer类,这时你如果再查看下Lucene自带的其他分词器,你会发现所有的分词器,比如KeywordAnalyzer,SimpleAnalyzer等等都继承了这个类,那么由此便可几乎可以肯定这个类便是Lucene提供外来分词器融入Lucene的一个准入证,然后我们看看Analyzer究竟是个什么东西?如下:

public abstract class Analyzer {
 public abstract TokenStream tokenStream(String fieldName, Reader reader);
	 public int getPositionIncrementGap(String fieldName){
	 return 0;
 }
}

原来是个抽象类!而抽象类主要就是干这活的,那就是提供一个通用的接口让别人去继承,而自己却懒得去干具体的活,这更证明了这点,要想自己写的分词器融入到Lucene中,必须继承这个类!接下来再看它的方法
public abstract TokenStream tokenStream(String fieldName, Reader reader);里的TokenStream字面意思理解为分词流,什么意思呢?按我自己的理解,流在java里是与内存联系在一起的,分词流的意思就是分词在内存中以流的形式存在,或者说如果我们要得到分词对象(Token),就要通过TokenStream这个东西来得到,这个解释听上去可能有些不大好理解,在这里,我尽可能地以自己的理解来解释,在后面中我也会拿出实际的代码来予以说明,在Lucene对TokenStream的英文解释为A TokenStream enumerates the sequence of tokens, either from fields of a document or from query text.如果有看得懂的朋友就不用看我的解释了,看这段英文就好了.
public abstract TokenStream tokenStream(String fieldName, Reader reader);这句中的第一个参数表示一个字段名,也就是你建索引的时候对应的字段名,比如:Field f = new Field("title","hello",Field.Store.YES, Field.Index.TOKENIZED);这句中的"title";而reader是java.io.Reader对象,顺便提一下,java.io.Reader也是一个抽象类,是用来读取字符流的,其用法大致为:

String s = "hello";
reader = new StringReader(s); //StringReader继承自Reader

那么这一整句表示的就是:返回一个TokenStream对象,而这个TokenStream对象是由一个字段和这个字段对应的要分词的文本所转换成的Reader对象决定的,这个解释同样听上去也比较拗口,不过之后我也会尽量用代码来予以说明的。关于Analyzer类中的public int getPositionIncrementGap(String fieldName),这个在分词器的编写过程中没有用得上,所以不是很重要,不过用已有的中文解释为:在建立索引时,处理重复词条的位移增量问题,至于英文注释很长,这里就不列出了,有源码的朋友可以自己去看吧。
好了,分析完Analyzer,我们再回到StandardAnalyzer来瞧一瞧吧,在StandardAnalyzer中,通过上面的分析,我们已经知道了public TokenStream tokenStream(String fieldName, Reader reader)是返回一个TokenStream对象,而这个TokenStream对象是由一个字段和这个字段对应的要分词的文本所转换成的Reader对象决定的,但是我们再看看方法体,发现还有好几行代码,下面分别予以简单说明:
1TokenStream result = new StandardTokenizer(reader);//表示用StandardTokenizer对这个要分词的reader进行处理(不知道为啥想起了《这个杀手不太冷》中的那个主人公回答那个小姑娘问他是干啥时候的回答:我是清道夫cleaner),然后返回一个TokenStream对象
2result = new StandardFilter(result);//表示对上面由"清道夫"进行清理后的TokenStream对象进行过滤(Filter)
3result = new LowerCaseFilter(result);//表示对上面由"StandardFilter"过滤后的TokenStream对象再进行次过滤(跟广告中所说的纯净水经过多少次过滤后的道理其实一样的)
4result = new StopFilter(result, stopSet);//接下来再进行次过滤,此次过滤后,达到了国家规定标准,此纯净水就可以喝了
5return result;//XXX牌纯净水可以正式上市出售了! 从上面的分析中可以得出这样一个结论,在一个分词器中:
1.有进行分词的Tokenizer(StandardTokenizer)
2.有进行过滤的Filter(比如LowerCaseFilter等)
所以总的结论为:一个分词器(Analyzer)包括一个Tokenizer和若干个Filter(不同的分词器所有的Filter个数不一定相同,就如同不同的纯净水品牌,过滤的次数不同一样)
现在问题就回到了此文的最初了,如何让Lucene认得自己编写的分词器并让分词器融入到Lucene温暖的大家庭中呢?很显然,我们要按照Lucene提供的准入规则来办,因此,如果你想写一个XXXAnalyzer自定义的分词器,那么你得:
1.继承一下Analyzer,让Lucene知道我是很诚心地想融入到这个温暖大家庭中的,public class XXXAnalyzer extends Analyzer
2.然后加入之后要干活呀,不能终日游手好闲,所以得努力干活呀:于是public TokenStream tokenStream(String fieldName, Reader reader),在这个方法中定义了你干活的工具:锄头Tokenizer(锄禾日当午,汗滴禾下土)和镰刀Filter(我割我割我割割割)
3.然后告诉Lucene,我干完了一件活(return result;)
Ok,就是这样了!让Lucene认识自己的分词器很简单吧?Lucene刚看上去的时候很严肃,让人摸不着脾气,其实熟悉后会发现这“人”挺不错的!
附:
1.细心的朋友一定会发现在StandardAnalyzer中还有好几段代码,是关于StopFilter的,由于这篇博文主要讲的是剖析StandardAnalyzer从而得知如何让Lucene认识自己的分词器,而StopFilter在其中显得并非是必须的,所以先暂时略过,留待以后有空的时候再补上。
2.关于XXXAnalyzer的中文命名,我这里是统一将XXXAnalyzer命名为分词器,而按照英文字面意思理解应为分析器,有的书上也是翻译为分析器,而我觉得我们更直接想到的是对中文进行分词,所以就直接命名为了分词器,实际上,Tokenizer才是分词器,也是构成XXXAnalyzer的一部分,但XXXAnalyzer的主要工作其实就是Tokenizer干的活,特此说明。


你可能感兴趣的:(Lucene 3 中写自己的分词器Analyzer)