Lucene.Net 初学笔记 - 索引

上次随笔写的Lucene.Net 初学笔记 - 介绍,有许多前辈让我知道了Lucene.Net已经不再更新,最后的版本写到2.9.2,不过只更新在svn上。我上次下载是官方正式发布的版本,只有2.0。若有兴趣下载最新版本看,可以从chunk下载:https://svn.apache.org/repos/asf/lucene/lucene.net/trunk/C%23/

另外eaglet前辈的hubbledotnet,是一个基于数据库的索引和搜索的引擎,它数据库的概念和用类似于SQL的查询语句,感觉都很实用,再加上它较于Lucene.Net的性能更优,以后将会更有前途。强烈支持eaglet能继续更新。

学Lucene.Net是因为它学习资源多,java版本文档对它也有参考意义。既然开始了,想还是快速把Lucene.Net过一般,看一下它到底是如何工作的,对以后学习其他引擎也有帮助。

今天看一下Lucene.Net建立索引的过程。记得上次提到方法

IndexWriter.AddDocument

但没有说如何构造它的参数 Document 对象。下面这段代码来自DemoLib项目中FileDocument文件:

   1:              // make a new, empty document
   2:              Document doc = new Document();
   3:              
   4:              // Add the path of the file as a field named "path".  Use a field that is 
   5:              // indexed (i.e. searchable), but don't tokenize the field into words.
   6:              doc.Add(new Field("path", f.FullName, Field.Store.YES, Field.Index.UN_TOKENIZED));
   7:              
   8:              // Add the last modified date of the file a field named "modified".  Use 
   9:              // a field that is indexed (i.e. searchable), but don't tokenize the field
  10:              // into words.
  11:              doc.Add(new Field("modified", DateTools.TimeToString(f.LastWriteTime.Ticks, DateTools.Resolution.MINUTE), Field.Store.YES, Field.Index.UN_TOKENIZED));
  12:              
  13:              // Add the contents of the file to a field named "contents".  Specify a Reader,
  14:              // so that the text of the file is tokenized and indexed, but not stored.
  15:              // Note that FileReader expects the file to be in the system's default encoding.
  16:              // If that's not the case searching for special characters will fail.
  17:              doc.Add(new Field("contents", new System.IO.StreamReader(f.FullName, System.Text.Encoding.Default)));

这里一共创建了3个字段(field)

1. path是文件路径,存储字段并索引,但是不会对它进行分词

2. modified是最后修改时间,存储字段并索引,也不会对它进行分词

3. contents是文件正文,不存储因为它太大,建立索引,对它进行分词

AddDocument方法其实做的事情就是对这三个字段建立索引,存储和建立逆向索引,就是从分词到文档的索引。

 

要了解索引,首先要知道下面几个概念:

index,就是索引,一个索引包含一连串的文档

document,文档,一个文档包含一连串的字段

field,字段,一个字段包含一连串的词(term)

term,词,就是一个字符串

inverted index, 逆向索引,就是根据term来索引document,类似于书籍最后的对词语的索引,映射到书中出现词的段落。

Segments,子索引,一个Lucene.Net的索引可以由多个segment组成。看Lucene.Net实现,每个segment是对应每个document的索引。

 

每个Segment index包含下面内容:

  • 字段名字
  • 存储的字段值
  • 字典
  • 词语出现的频率信息
  • 词语映射到文档中的信息
  • Normalization factors 用来计算字段的被查找到的分数
  • 词向量

 

最终这些信息都将会以文件形式保存,下面简单介绍一下索引文件的结构:

 

Segments文件,是多个segment的索引,主要包含了Lucene.Net的版本信息,和每个segment的命名,大小。

segment的命名是用递增+1的名字:

   1:  return "_" + SupportClass.Number.ToString(segmentInfos.counter++, SupportClass.Number.MAX_RADIX);

以下其他后缀名的文件,都是以segment名字作为文件名。

 

Field Infos (.fnm)文件,是对多个field的索引,包含field的数量,以及各个field的名字和field的属性信息。

field属性信息是以bit来存储的,用它可以知道field是否建立索引,term向量相关信息, 以及其他一些属性

   1:          public void  Write(IndexOutput output)
   2:          {
   3:              output.WriteVInt(Size());
   4:              for (int i = 0; i < Size(); i++)
   5:              {
   6:                  FieldInfo fi = FieldInfo(i);
   7:                  byte bits = (byte) (0x0);
   8:                  if (fi.isIndexed)
   9:                      bits |= IS_INDEXED;
  10:                  if (fi.storeTermVector)
  11:                      bits |= STORE_TERMVECTOR;
  12:                  if (fi.storePositionWithTermVector)
  13:                      bits |= STORE_POSITIONS_WITH_TERMVECTOR;
  14:                  if (fi.storeOffsetWithTermVector)
  15:                      bits |= STORE_OFFSET_WITH_TERMVECTOR;
  16:                  if (fi.omitNorms)
  17:                      bits |= OMIT_NORMS;
  18:                  output.WriteString(fi.name);
  19:                  output.WriteByte(bits);
  20:              }
  21:          }

 

FieldIndex (.fdx)文件,是field的index文件,存储了每个field值在field值文件中的位置信息

 

FieldData (.fdt)文件,field值文件,需要存储字段的数量,每个字段的编号,属性bits,和值数据

 

在说term字典文件之前,先看一下Lucene.Net是如何invert document的。具体可以看Lucene.Net.Index.DocumentWriter.InvertDocument(Document doc)方法。我把简化的代码贴在这里:

   1:          private void  InvertDocument(Document doc)
   2:          {
   3:              foreach(Field field in doc.Fields())
   4:              {
   5:                  // Get field information
   6:                  if (length > 0)
   7:                      position += analyzer.GetPositionIncrementGap(fieldName);
   8:                  
   9:                  if (field.IsIndexed())
  10:                  {
  11:                      if (!field.IsTokenized())
  12:                      { // add field token information
  13:                      }
  14:                      else
  15:                      {
  16:                          System.IO.TextReader reader; // find or make Reader
  17:                          if (field.ReaderValue() != null)
  18:                              reader = field.ReaderValue();
  19:                          else if (field.StringValue() != null)
  20:                              reader = new System.IO.StringReader(field.StringValue());
  21:                          else
  22:                              throw new System.ArgumentException("field must have either String or Reader value");
  23:                          
  24:                          // Tokenize field and add to postingTable
  25:                          TokenStream stream = analyzer.TokenStream(fieldName, reader);
  26:                          try
  27:                          {
  28:                              Token lastToken = null;
  29:                              for (Token t = stream.Next(); t != null; t = stream.Next())
  30:                              {
  31:                                  // add token information
  32:                          }
  33:                          finally
  34:                          {
  35:                              stream.Close();
  36:                          }
  37:                      }
  38:                  }
  39:              }
  40:          }

Analyzer的TokenStream就是用来把field value分词成token进而转化成term。具体分词算法根据不同的analyzer而不同,这里不详述了。

被分词以后的term将会以附带位置的信息存储在一个list中。在这个list将保存每个term和他们每次出现在document中position的信息,当然position的数量也表示term出现的频率。下面说到和term字典相关的文件就是把这个list的数组结构保存在文件中:

 

TermInfoFile (.tis) term信息文件, 是对所有term的一个索引,主要包含了term数量,以及每个term的一些信息,这个信息有些复杂,需要详述:

从文档中看来,它包含很多信息

TermInfo --> <Term, DocFreq, FreqDelta, ProxDelta, SkipDelta>

Term --> <PrefixLength, Suffix, FieldNum>

Suffix --> String

PrefixLength, DocFreq, FreqDelta, ProxDelta, SkipDelta
--> VInt

关于PrefixLength, Suffix, FieldNum,用代码解释比较清楚:

   1:  private void WriteTerm(Term term)
   2:  {
   3:      int start = StringHelper.StringDifference(this.lastTerm.text, term.text);
   4:      int length = term.text.Length - start;
   5:      this.output.WriteVInt(start);
   6:      this.output.WriteVInt(length);
   7:      this.output.WriteChars(term.text, start, length);
   8:      this.output.WriteVInt(this.fieldInfos.FieldNumber(term.field));
   9:      this.lastTerm = term;
  10:  }
  11:   

DocFreq, FreqDelta, ProxDelta, SkipDelta

主要是记录term指向到频度文件和位置文件的位置。

   1:      this.output.WriteVInt(ti.docFreq);
   2:      this.output.WriteVLong(ti.freqPointer - this.lastTi.freqPointer);
   3:      this.output.WriteVLong(ti.proxPointer - this.lastTi.proxPointer);
   4:      if (ti.docFreq >= this.skipInterval)
   5:      {
   6:          this.output.WriteVInt(ti.skipOffset);
   7:      }

 

FreqFile (.frq) 频度文件,存储了每个term出现在每个document的编号和出现的次数。

ProxFile (.prx) 位置信息文件,存储了term出现在document中出现次数和位置的信息。

 

最后是建立对词向量的索引文件:

DocumentIndex (.tvx) 文件向量索引文件,记录了对(.tvd)文件地址的索引。

Document (.tvd) 文件向量文件, 记录了每个document中每个field对应到.tvf文件中的地址。

Field (.tvf) 字段向量文件,记录了每个字段中词向量的信息,包括一个词列表和他们出现的次数和位置信息

 

今天先说到这里,还有一些概念并没有弄得很明白,包括文档中提到的Interval,SkipDelta, Normalization Factors, 以及词向量的具体应用,为什么它和前面的一些索引文件保存的信息是重复的。这些在以后的文章中再解释吧。

这篇文章主要内容来自:http://lucene.apache.org/java/2_1_0/fileformats.html

转载于:https://www.cnblogs.com/hongyes/archive/2010/07/22/1782627.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值