`

【Lucene3.0 初窥】索引创建(3):DocumentWriter 处理流程二

阅读更多

 

上接《索引创建(2):DocumentWriter处理流程一

 

1.3.2 第二车间——DocInverterPerField

 

DocInverterPerField 负责对DocFieldProcessorPerThread对象的Fieldable[]数组的内容建立倒排索引,也就是处理同名字的所有Field。但实际上这个类主要解决的是前期工作,比如分词,统计位置信息等。倒排索引结构的核心的工作由TermsHashPerField FreqProxTermsWriterPerField (第三车间 ) 来完成。这两个类将在后面的专题中再提及。

 

DocInverterPerField 核心方法是processFields(Fieldable[] fields)。它负责这几个方面的工作:

(1)将field的value值切分成一个个term

(2)调用FieldInvertState类来存储和统计当前field的所有term出现的位置position和offset信息,并计算该field的boost分值,为所有相同名字的fields的boost与文档的boost的乘积。

(3) 调用TermsHashPerField和 FreqProxTermsWriterPerField 每个term 加入倒排索引结构。

 

 

Part I src code:

 public void processFields(final Fieldable[] fields, final int count)  {

    //FieldInvertState类的职责就是跟踪将要加入索引(index)结构中的词语的位置(position/offset)
    //首先初始化FieldInvertState类的数据域。
    fieldState.reset(docState.doc.getBoost());
    
    //确定Field允许的最大词语数量10000
    final int maxFieldLength = docState.maxFieldLength;

    //确定fields数组是否需要索引(isIndexed)
    //如果有一个field需要索引,则doInvert=true
    final boolean doInvert = consumer.start(fields, count);

    //取出fields[]中的取出当前field(这些field名字相同)
    for(int i=0;i<count;i++) {
        final Fieldable field = fields[i];
        //当前field需要索引且整个fields数组都需要检索
        if (field.isIndexed() && doInvert) {
             
             //如果有多个同名的field,则将后面的field的value接到前面的field之后
             //即field[1]的第一个token的词语位置要从field[0]开始算起。             
             if (fieldState.length > 0)
                  fieldState.position += docState.analyzer.getPositionIncrementGap(fieldInfo.name);
       

             //当前field不需要分词
             if(!field.isTokenized()) {
                   ....
                   //则直接将整个field的值交给TermsHashPerField建立索引
                   consumer.start(field);
                   try {
                         consumer.add();
                         success = true;
                   } finally {
                   if (!success)
                       docState.docWriter.setAborting();
                   ....
              }else {//当前field需要分词
                   final TokenStream stream;
                   //确定field在创建的时候是否已经有了一个内容词语的tokenStream
                   final TokenStream streamValue = field.tokenStreamValue();
                   //field已经有分好词的tokenStream
                   if (streamValue != null) 
                        stream = streamValue;
                   else {//field没有分好词的tokenStream
                       final Reader reader;
                       //确定field的内容是否是Reader类型
                       final Reader readerValue = field.readerValue();
                       //field内容是Reader类型
                       if (readerValue != null)
                           reader = readerValue;
                       else {
                           //filed内容不是Reader类型,则判断是否是String
                           String stringValue = field.stringValue();
                           if (stringValue == null)
                               throw new IllegalArgumentException("field must have either TokenStream, String or Reader value");
                           perThread.stringReader.init(stringValue);
                           reader = perThread.stringReader;
                       }
                    //用分析器处理当前field(进行分词和过滤),并加入到postingTable
                    stream = docState.analyzer.reusableTokenStream(fieldInfo.name, reader);
                  }


               ......第二部分源码.....


        }//end if(需要索引)
        
        consumer.finish();
        endConsumer.finish();

    }//end for(每一个field)
}//end processFields

 

第一部分源码的主要作用就是根据每一个需要检索的field的不同操作方式进行处理。如果field不需要分词,则直接将filed交给TermsHashPerField建立索引结构(code line: 30, 32)。如果field需要分词,则首先判断field的value是不是Reader类型(分析器Analyzer只接受Reader类型数据),不是则将value字符串值包装成Reader类型(code line:57)。再让Analyzer分词得到TokenStream stream(code line : 61)。然后将stream中的每一个token交给 TermsHashPerField建立索引结构(请看后面的第二部分代码)。

 

我们用上一节的doc1的例子来查看这个stream的结果,其中doc1通过上一节加工成了DocFieldProcessorPerThread fields[]数组。而fields[0]就是指doc1中名字为cotent的field集合,这个集合有两个content field。

 

content field 1: The lucene is a good IR. I hope I can lean.

stream 的结果显示(已经去停用词了):

token 
  type 
offset
pos
lucene <ALPHANUM> (4,10)
2
good <ALPHANUM> (16,20)
3
ir <ALPHANUM> (21,23) 1
i <ALPHANUM> (25,26)
1
hope <ALPHANUM> (27,31) 1
i <ALPHANUM> (32,33)
1
can <ALPHANUM> (34,37)
1
lean <ALPHANUM> (38,42) 1

 

content field 2: Lucene 3.0 like a teacher. I love it.

stream 的结果显示(已经去停用词了):

token  type
offset pos
lucene <ALPHANUM> (0,7) 1
3.0 <NUM> (8,11) 1
like <ALPHANUM> (12,16) 1
teacher <ALPHANUM> (19,26) 2
i <ALPHANUM> (28,29) 1
love <ALPHANUM> (30,34) 1

 


Part II src code:

...... 第一部分.....

// 将TokenStream内部指针指向第一个token
stream.reset();

final int startLength = fieldState.length;

try {
   //记录当前token首字母在文本中的位置,如果token是TokenStream中的第一个词语,则offsetEnd=-1
   int offsetEnd = fieldState.offset-1;
   //获取分词后tokenStream的每一个token的全部信息
   boolean hasMoreTokens = stream.incrementToken();

    fieldState.attributeSource = stream;
   //得到当前token的OffsetAttribute属性信息
   OffsetAttribute offsetAttribute =fieldState.attributeSource.addAttribute(OffsetAttribute.class);
   //得到当前token的PositionIncrementAttribute属性信息
   PositionIncrementAttribute posIncrAttribute =fieldState.attributeSource.addAttribute(PositionIncrementAttribute.class);
   //利用TermsHashPerField将每一个token加入倒排索引结构
   consumer.start(field);      
   for(;;) {
      //tokenStream结束
      if (!hasMoreTokens) break;
      
      //得到当前token的positionIncreament属性
      final int posIncr = posIncrAttribute.getPositionIncrement();     
      //此时fieldState.position表示当前token所在原文本中的词语位置,即token前面有多少个词语
      fieldState.position += posIncr;
      //positionIncreament属性计算的时候就是相隔的词语数量+1,因此统计当前token前面的词语数量的时候,要减1
      if (fieldState.position > 0) {
                fieldState.position--;
      }
      
      if (posIncr == 0)
                fieldState.numOverlap++;
      try {
          //利用TermsHashPerField将当前token以及fieldState当前所记录的位置信息一并加入进倒排索引结构中           
          consumer.add();
           success = true;
      } finally {
           if (!success)
               docState.docWriter.setAborting();
      }
      //准备记录下一个token,因此将当前token算入进去
      fieldState.position++;
      //记录当前token的尾字母在原文本中所在的位置
      offsetEnd = fieldState.offset + offsetAttribute.endOffset();       
      //fieldState.length记录了当前已经处理了的token数量,如果超过了允许的最大数量,则后面的词语将被丢弃,不再加入到索引中。
      if (++fieldState.length >= maxFieldLength) {
                if (docState.infoStream != null)
                  docState.infoStream.println("maxFieldLength " +maxFieldLength+ " reached for field " + fieldInfo.name + ", ignoring following tokens");
                break;
       }

      //取下一个token
      hasMoreTokens = stream.incrementToken();
    }
    stream.end();
            
 } finally {
     stream.close();
 }
 

第二部分源码的主要作用就是循环得到stream(第一部分代码)中的每一个token(code line: 21),计算token在原始文本中的位置(code line: 28,31),并保存在fieldState.position和fieldState.offset中。同时token和fieldState中的统计信息交给TermsHashPerField建立倒排索引结构(code line: 38)。

 

总结 ,下图 展示了 DocInverterPerField 的作用。它会把不需要分词的field以红色方框的结构(field value)传给TermsHashPerField FreqProxTermsWriterPerField 来建立索引。而把需要分词的content field变成一个个蓝色方框的结构(token && position)来建立索引,接下来就是对token建立倒排索引的过程了。请参见《索引创建(4):DocumentWriter 处理流程三 》。

 

注意,上图蓝色方框的箭头并不是指DocInverterPerField会把他们建立成链表结构。事实上,这些箭头只是为了表明一个个token依次被 TermsHashPerField加入索引结构的。另外,相同名字的field中的词语会依次处理,就如同上面fields[0]和fields[1]。

 

2
0
分享到:
评论

相关推荐

    lucene3.0 lucene3.0

    lucene3.0 lucene3.0 lucene3.0 lucene3.0 lucene3.0

    Lucene3.0创建索引

    ### Lucene3.0创建索引 在Lucene3.0中创建索引是一个关键功能,可以帮助用户快速地检索和管理大量的文本数据。本篇文章将详细介绍如何使用Lucene3.0来创建索引,并通过一个具体的例子来演示整个过程。 #### 一、...

    lucene3.0庖丁+索引搜索程序

    《深入剖析Lucene3.0:庖丁解牛与索引搜索实践》 在IT行业中,搜索引擎技术扮演着至关重要的角色,而Lucene作为一个开源全文检索库,为开发者提供了强大的文本搜索功能。本文将深入探讨Lucene3.0版本,结合“庖丁解...

    Lucene3.0之查询类型详解

    【Lucene3.0查询类型详解】 在Lucene3.0中,查询处理是一个关键环节...以上就是Lucene3.0查询处理的基本原理、模型、流程以及主要查询类型的详细说明。通过这些知识,开发者可以有效地构建和执行复杂的信息检索任务。

    Lucene 3.0 原理与代码分析完整版

    二、Lucene索引流程 1. 文档分词:Lucene使用Analyzer对输入的文档进行分词,生成Token流。 2. 字符串到Term:将分词结果转化为Term对象,每个Term代表一个唯一的词汇项。 3. 建立Term Frequency(TF):记录每个...

    lucene 3.0 API 中文帮助文档 chm

    lucene 3.0 API中文帮助,学习的人懂得的

    lucene3.0核心jar包

    在 Lucene 3.0 中,索引过程包括分词、字段处理、文档ID分配等步骤,生成的索引文件包括词典、Posting List、Doc IDs 等组件。 2. **分词器(Analyzer)**:Lucene 提供了多种分词器,如 StandardAnalyzer、...

    lucene3.0 实例

    生成索引的类通常会读取文件内容,创建 Lucene 文档,然后将这些文档添加到索引中。这个过程包括以下几个步骤: 1. 初始化索引目录:使用 `FSDirectory.open()` 创建指向索引存储位置的目录对象。 2. 创建索引...

    Lucene3.0全文信息检索

    2. **多线程支持**:在3.0版本中,Lucene增强了多线程处理能力,允许在并发环境中更有效地创建和更新索引。 3. **内存管理优化**:Lucene 3.0改进了内存使用策略,降低了内存占用,同时提升了索引和搜索的性能。 4...

    lucene3.0资料包

    Lucene3.0包含了标准的分词器(StandardAnalyzer),它对英文文本进行了高效的处理,如去除停用词、词干提取等。此外,Lucene还支持自定义分词器,允许开发者针对特定语言或业务需求进行定制。 3. **查询解析**: ...

    lucene3.0-api.CHM

    二、Lucene 3.0 API组件 1. Directory:存储索引的接口,如FSDirectory用于磁盘上的目录,RAMDirectory用于内存中的目录。 2. IndexWriter:负责创建和更新索引的主要类,它可以添加、删除和修改文档。 3. ...

    lucene3.0使用介绍及实例

    在这个例子中,我们创建了一个索引,包含一个文档,然后搜索包含"Lucene 3.0"的文档。这个简单的示例展示了Lucene的基本用法,实际应用中可以根据需要扩展,例如添加更多的文档字段、实现更复杂的查询逻辑,或者使用...

    lucene3.0 分词器

    lucene3.0 中文分词器, 庖丁解牛

    lucene 2.0 api以及lucene 3.0 api

    1. **索引构建**: Lucene 2.0 提供了 `IndexWriter` 类,用于创建和更新索引。开发者可以使用 `Document` 类来封装待索引的数据,然后通过 `addDocument()` 方法添加到索引中。 2. **查询构造**: 通过 `QueryParser...

    Lucene3.0增删改查和关键字高亮实例

    首先,我们要了解**创建索引**的基本流程。在Lucene中,创建索引涉及到以下几个步骤: 1. 创建`Directory`对象,这是存储索引的容器,可以是硬盘上的文件系统或内存中的目录。 2. 初始化`IndexWriter`,设置相应的...

    lucene3.0全文检索入门实例

    **Lucene 3.0 全文检索入门实例** Lucene 是一个开源的全文检索库,由 Apache 软件基金会开发。它提供了一个高级、灵活的搜索功能框架,允许开发者在自己的应用中轻松地集成全文检索功能。本文将重点介绍如何使用 ...

    Lucene 3.0完成入门

    通过以上内容的学习,你可以掌握 Lucene 3.0 的基本操作,包括如何创建索引、执行查询、优化搜索性能等。同时,了解 Compass 如何简化 Lucene 的使用,以及如何结合实际业务需求来设计和实现一个搜索引擎。在实践中...

Global site tag (gtag.js) - Google Analytics