Lucene源码分析-- Analyzer

 

原文出处:

http://lqgao.spaces.live.com/blog/cns!3BB36966ED98D3E5!437.entry?_c11_blogpart_blogpart=blogview&_c=blogpart#permalink



本文主要分析一下 Lucene输入部分——Analyzer(分析器)。为什么要有Analyzer部分呢?打个比方,人体在消化食物的时候,是不是都要把食物分解掉?食物在肠道里面,被分解成葡萄糖、氨基酸、脂肪等等。变成小块以后,才容易被吸收并加以利用。Lucene也有类似的过程:把文本分解成更小的单元,有词、标点符号、分割符号,甚至还有网站名等等。Analyzer就好比是人体的肠道,它的职责就是把输入的文本切成小的单元。

先看一段代码吧:
#0001  public static void TestAnalyzer()
#0002  {
#0003      Analyzer []analyzers = new Analyzer[4];
#0004      analyzers[0] = new WhitespaceAnalyzer();
#0005      analyzers[1] = new SimpleAnalyzer();
#0006      analyzers[2] = new StopAnalyzer();
#0007      analyzers[3] = new StandardAnalyzer();
#0008      String text = "This is a test document. For more info, please visit Victor's Blog: http://lqgao.spaces.msn.com. ";
#0009      for(int i=0; i<analyzers.Length; i++)
#0010      {
#0011          DumpAnalyzer(analyzers[i], new StringReader(text));
#0012      }
#0013  }
#0014 
#0015  public static void DumpAnalyzer(Analyzer analyzer, TextReader reader)
#0016  {
#0017      TokenStream stream = analyzer.TokenStream(reader);
#0018      Token token;
#0019      System.Console.WriteLine(analyzer + " :");
#0020      while ((token = stream.Next()) != null)
#0021      {
#0022          System.Console.Write("[" + token.TermText() + "]");
#0023      }
#0024      System.Console.WriteLine();
#0025      System.Console.WriteLine();
#0026  }
运行结果如下:
Lucene.Net.Analysis.WhitespaceAnalyzer :
[This][is][a][test][document.][For][more][info,][please][visit][Victor's][Blog:]
[http://lqgao.spaces.msn.com.]

Lucene.Net.Analysis.SimpleAnalyzer :
[this][is][a][test][document][for][more][info][please][visit][victor][s][blog][h
ttp][lqgao][spaces][msn][com]

Lucene.Net.Analysis.StopAnalyzer :
[test][document][more][info][please][visit][victor][blog][http][lqgao][spaces][m
sn][com]

Lucene.Net.Analysis.Standard.StandardAnalyzer :
[test][document][more][info][please][visit][victor][blog][http][lqgaospacesmsnco
m]
好,让咱们来分析一下。Lucene中默认提供4个Analyzer:SimpleAnalyzer, StandardAnalyzer, StopAnalyzer, WhitespaceAnalyzer。至于这4个有什么区别,听我慢慢道来。
WhitespaceAnalyzer似乎什么都不做,就是按照white space (空格符号)把文本分开——这样做最省力,最简单。
SimpleAnalyzer 则比WhitespaceAnalyzer进步一些,至少不管大写还是小写的字母,统统变成小写形式。这样做的好处也很明显,不管输入是This tHis还是 THIS thIS,最后都统一为this,便于匹配。除了统一大小写外,SimpleAnalyzer还把标点符号处理了,或者说SimpleAnalyzer是按照标点符号分割单词的。比如`documents.’在SimpleAnalyzer的结果中变为`document’。
StopAnalyzer 看起来和SimpleAnalyzer非常相似,只不过,结果中有一些词被去掉了,比如‘this’, ‘is’, ‘a’, ‘for’等——这些大量出现但没有实际意义的词通常被称为stop word(停用词),并被去掉,不加入索引中。因为这样的词数量很大,但并不能很好的区分文档的内容。去掉stop word能减少索引的规模。
StandardAnalyzer做得要复杂一些了。像”Victor’s”这样的词,被处理为’victor’,并没有”’s”,而且网址也被处理了。稍后我们分析StandardAnalyzer的功能。这几个Analyzer的继承关系如图 1所示。
 
图 1几种analyzer的类层次图
现在回头再看看Analyzer们是怎么工作的(#0015~#0026)。其实Analyzer是一个工厂模式(Factory Pattern),见#0017。使用时需要其生成一个TokenStream的对象。TokenStream,顾名思义,表示token流,即一个 token序列。每个token都是Token类型的。#0020~#0023展现TokenStream的调用方式。
接下来让我们一步一步地展开Analyzers的细节。既然Token是TokenStream组成的元素,让我们先来看看它的“庐山真面目”。
#0001  public sealed class Token
#0002  {
#0003      internal System.String termText; // the text of the term
#0004      internal int startOffset; // start in source text
#0005      internal int endOffset; // end in source text
#0006      internal System.String type_Renamed_Field = "word"; // lexical type
#0007 
#0008      private int positionIncrement = 1;
#0009 
#0010      public Token(System.String text, int start, int end)
#0011      public Token(System.String text, int start, int end, System.String typ)
#0012 
#0013      public void  SetPositionIncrement(int positionIncrement)
#0014      public int GetPositionIncrement()
#0015      public System.String TermText()
#0016      public int StartOffset()
#0017      public int EndOffset()
#0018      public System.String Type()
#0019  }
可以看出,Token存储了term的字符串(#0003),并记录下起始和终止位置(#0004~#0005),此外还有一个类型信息(#0006)。DumpAnalyzer中调用了TermText()获取字符串信息。
然后看看TokenStream:
#0001  public abstract class TokenStream
#0002  {
#0003      /// <summary>Returns the next token in the stream, or null at EOS. </summary>
#0004      public abstract Token Next();
#0005 
#0006      /// <summary>Releases resources associated with this stream. </summary>
#0007      public virtual void  Close()
#0008      {}
#0009  }
TokenStream是一个抽象类,接口只有两个:Next()和Close()。Next()返回当前的token,并指向下一个token;没有token则返回null。
Analyzer也是一个抽象类。默认的TokenStream() (#0005)就是构造并返回一个TokenStream的对象。
#0001  public abstract class Analyzer
#0002  {
#0003      public virtual TokenStream TokenStream(System.IO.TextReader reader)
#0004      {
#0005          return TokenStream(null, reader);
#0006      }
#0007  }
再看它的一个子类WhitespaceTokenizer:
#0001  public class WhitespaceTokenizer:CharTokenizer
#0002  {
#0003      public WhitespaceTokenizer(System.IO.TextReader in_Renamed):base(in_Renamed)
#0004      {}
#0005      protected internal override bool IsTokenChar(char c)
#0006      {
#0007          return !System.Char.IsWhiteSpace(c);
#0008      }
#0009  }
#0010 
#0021  public abstract class CharTokenizer : Tokenizer
#0022  {
#0023      public CharTokenizer(System.IO.TextReader input) : base(input)
#0024      {}
#0025 
#0026      private int offset = 0, bufferIndex = 0, dataLen = 0;
#0027      private const int MAX_WORD_LEN = 255;
#0028      private const int IO_BUFFER_SIZE = 1024;
#0029      private char[] buffer = new char[MAX_WORD_LEN];
#0030      private char[] ioBuffer = new char[IO_BUFFER_SIZE];
#0031 
#0032      protected internal abstract bool IsTokenChar(char c);
#0033      protected internal virtual char Normalize(char c)
#0034      {
#0035          return c;
#0036      }
#0037      public override Token Next()
#0038      {
#0039          int length = 0;
#0040          int start = offset;
#0041          while (true)
#0042          {
#0043              char c;
#0044 
#0045              offset++;
#0046              if (bufferIndex >= dataLen)
#0047              {
#0048                  dataLen = input.Read((System.Char[]) ioBuffer, 0, ioBuffer.Length);
#0049                  bufferIndex = 0;
#0050              }
#0051              ;
#0052              if (dataLen <= 0)
#0053              {
#0054                  if (length > 0)
#0055                      break;
#0056                  else
#0057                      return null;
#0058              }
#0059              else
#0060                  c = ioBuffer[bufferIndex++];
#0061 
#0062              if (IsTokenChar(c))
#0063              {
#0064                  // if it's a token char
#0065 
#0066                  if (length == 0)
#0067                      // start of token
#0068                      start = offset - 1;
#0069 
#0070                  buffer[length++] = Normalize(c); // buffer it, normalized
#0071 
#0072                  if (length == MAX_WORD_LEN)
#0073                      // buffer overflow!
#0074                      break;
#0075              }
#0076              else if (length > 0)
#0077                  // at non-Letter w/ chars
#0078                  break; // return 'em
#0079          }
#0080 
#0081          return new Token(new System.String(buffer, 0, length), start, start + length);
#0082      }
#0083  }
#0084 
#0085 
#0086  public abstract class Tokenizer : TokenStream
#0087  {
#0088      protected internal System.IO.TextReader input;
#0089      protected internal Tokenizer()
#0090      {}
#0091      protected internal Tokenizer(System.IO.TextReader input)
#0092      {
#0093          this.input = input;
#0094      }
#0095      public override void  Close()
#0096      {
#0097          input.Close();
#0098      }
#0099  }
几个类之间的关系:WhitespaceTokenizerCharTokenizerTokenizerTokenStream。而 CharTokenizer.Next()是一个关键(#0037~#0083)。它从缓冲区中找到分割符(#0062),然后用识别出来的字符串 (#0070)生成一个Token对象(#0081)。其余的Tokenizer只要定义不同的分割符号集合(#0032)就可以了。例如 WhitespaceTokenizer只要告诉Next()“只要不是white space就是分割符号”就可以了(#0005~#0008)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值