lucene--DocInverterPerField/DocInverterPerField

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:

Java代码   收藏代码
  1.  public void processFields(final Fieldable[] fields, final int count)  {  
  2.   
  3.     //FieldInvertState类的职责就是跟踪将要加入索引(index)结构中的词语的位置(position/offset)  
  4.     //首先初始化FieldInvertState类的数据域。  
  5.     fieldState.reset(docState.doc.getBoost());  
  6.       
  7.     //确定Field允许的最大词语数量10000  
  8.     final int maxFieldLength = docState.maxFieldLength;  
  9.   
  10.     //确定fields数组是否需要索引(isIndexed)  
  11.     //如果有一个field需要索引,则doInvert=true  
  12.     final boolean doInvert = consumer.start(fields, count);  
  13.   
  14.     //取出fields[]中的取出当前field(这些field名字相同)  
  15.     for(int i=0;i<count;i++) {  
  16.         final Fieldable field = fields[i];  
  17.         //当前field需要索引且整个fields数组都需要检索  
  18.         if (field.isIndexed() && doInvert) {  
  19.                
  20.              //如果有多个同名的field,则将后面的field的value接到前面的field之后  
  21.              //即field[1]的第一个token的词语位置要从field[0]开始算起。               
  22.              if (fieldState.length > 0)  
  23.                   fieldState.position += docState.analyzer.getPositionIncrementGap(fieldInfo.name);  
  24.          
  25.   
  26.              //当前field不需要分词  
  27.              if(!field.isTokenized()) {  
  28.                    ....  
  29.                    //则直接将整个field的值交给TermsHashPerField建立索引  
  30.                    consumer.start(field);  
  31.                    try {  
  32.                          consumer.add();  
  33.                          success = true;  
  34.                    } finally {  
  35.                    if (!success)  
  36.                        docState.docWriter.setAborting();  
  37.                    ....  
  38.               }else {//当前field需要分词  
  39.                    final TokenStream stream;  
  40.                    //确定field在创建的时候是否已经有了一个内容词语的tokenStream  
  41.                    final TokenStream streamValue = field.tokenStreamValue();  
  42.                    //field已经有分好词的tokenStream  
  43.                    if (streamValue != null)   
  44.                         stream = streamValue;  
  45.                    else {//field没有分好词的tokenStream  
  46.                        final Reader reader;  
  47.                        //确定field的内容是否是Reader类型  
  48.                        final Reader readerValue = field.readerValue();  
  49.                        //field内容是Reader类型  
  50.                        if (readerValue != null)  
  51.                            reader = readerValue;  
  52.                        else {  
  53.                            //filed内容不是Reader类型,则判断是否是String  
  54.                            String stringValue = field.stringValue();  
  55.                            if (stringValue == null)  
  56.                                throw new IllegalArgumentException("field must have either TokenStream, String or Reader value");  
  57.                            perThread.stringReader.init(stringValue);  
  58.                            reader = perThread.stringReader;  
  59.                        }  
  60.                     //用分析器处理当前field(进行分词和过滤),并加入到postingTable  
  61.                     stream = docState.analyzer.reusableTokenStream(fieldInfo.name, reader);  
  62.                   }  
  63.   
  64.   
  65.                ......第二部分源码.....  
  66.   
  67.   
  68.         }//end if(需要索引)  
  69.           
  70.         consumer.finish();  
  71.         endConsumer.finish();  
  72.   
  73.     }//end for(每一个field)  
  74. }//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 offsetpos
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:

Java代码   收藏代码
  1. ...... 第一部分.....  
  2.   
  3. // 将TokenStream内部指针指向第一个token  
  4. stream.reset();  
  5.   
  6. final int startLength = fieldState.length;  
  7.   
  8. try {  
  9.    //记录当前token首字母在文本中的位置,如果token是TokenStream中的第一个词语,则offsetEnd=-1  
  10.    int offsetEnd = fieldState.offset-1;  
  11.    //获取分词后tokenStream的每一个token的全部信息  
  12.    boolean hasMoreTokens = stream.incrementToken();  
  13.   
  14.     fieldState.attributeSource = stream;  
  15.    //得到当前token的OffsetAttribute属性信息  
  16.    OffsetAttribute offsetAttribute =fieldState.attributeSource.addAttribute(OffsetAttribute.class);  
  17.    //得到当前token的PositionIncrementAttribute属性信息  
  18.    PositionIncrementAttribute posIncrAttribute =fieldState.attributeSource.addAttribute(PositionIncrementAttribute.class);  
  19.    //利用TermsHashPerField将每一个token加入倒排索引结构  
  20.    consumer.start(field);        
  21.    for(;;) {  
  22.       //tokenStream结束  
  23.       if (!hasMoreTokens) break;  
  24.         
  25.       //得到当前token的positionIncreament属性  
  26.       final int posIncr = posIncrAttribute.getPositionIncrement();       
  27.       //此时fieldState.position表示当前token所在原文本中的词语位置,即token前面有多少个词语  
  28.       fieldState.position += posIncr;  
  29.       //positionIncreament属性计算的时候就是相隔的词语数量+1,因此统计当前token前面的词语数量的时候,要减1  
  30.       if (fieldState.position > 0) {  
  31.                 fieldState.position--;  
  32.       }  
  33.         
  34.       if (posIncr == 0)  
  35.                 fieldState.numOverlap++;  
  36.       try {  
  37.           //利用TermsHashPerField将当前token以及fieldState当前所记录的位置信息一并加入进倒排索引结构中             
  38.           consumer.add();  
  39.            success = true;  
  40.       } finally {  
  41.            if (!success)  
  42.                docState.docWriter.setAborting();  
  43.       }  
  44.       //准备记录下一个token,因此将当前token算入进去  
  45.       fieldState.position++;  
  46.       //记录当前token的尾字母在原文本中所在的位置  
  47.       offsetEnd = fieldState.offset + offsetAttribute.endOffset();         
  48.       //fieldState.length记录了当前已经处理了的token数量,如果超过了允许的最大数量,则后面的词语将被丢弃,不再加入到索引中。  
  49.       if (++fieldState.length >= maxFieldLength) {  
  50.                 if (docState.infoStream != null)  
  51.                   docState.infoStream.println("maxFieldLength " +maxFieldLength+ " reached for field " + fieldInfo.name + ", ignoring following tokens");  
  52.                 break;  
  53.        }  
  54.   
  55.       //取下一个token  
  56.       hasMoreTokens = stream.incrementToken();  
  57.     }  
  58.     stream.end();  
  59.               
  60.  } finally {  
  61.      stream.close();  
  62.  }  
 

第二部分源码的主要作用就是循环得到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]。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值