首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 互联网 >

【Lucene3.0 初窥】目录创建(2):DocumentWriter 处理流程一

2012-10-09 
【Lucene3.0 初窥】索引创建(2):DocumentWriter 处理流程一上接《索引创建(1): IndexWriter索引器》?1.3?? 索

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

上接《索引创建(1): IndexWriter索引器》

?

1.3?? 索引创建过程

?

DocumentsWriter是由IndexWriter调用来负责对多个document建立索引的核心类,但整个索引过程并不是由一个对象来完成的。而是有一系列的对象组成的处理链(IndexingChain)来完成的(这个过程就像流水线生产汽车)。下面是DocumentWriter开始建立索引的源代码。

//由IndexWriter调用的方法boolean addDocument(Document doc, Analyzer analyzer){      return updateDocument(doc, analyzer, null);}boolean updateDocument(Document doc, Analyzer analyzer, Term delTerm){    final DocumentsWriterThreadState state = getThreadState(doc, delTerm);    final DocState docState = state.docState;    docState.doc = doc;    docState.analyzer = analyzer;    boolean success = false;    try {          //调用处理链的源头DocFieldProcessorPerThread开始对Document对象建立索引结构          final DocWriter perDoc = state.consumer.processDocument();          finishDocument(state, perDoc);          success = true;     }       .......} 

?

1.3.1? 第一车间——DocFieldProcessorPerThread

?

DocFieldProcessorPerThread类是索引创建处理链的第一步。其基本任务:将document对象原料中所有相同名字的field合并成一个DocFieldProcessorPerThread对象,然后更新FieldInfo信息,最后对不同名字的Field构成一个DocFieldProcessorPerThread[]对象数组。这个数组就是下一个车间DocInverterPerField要加工的原料了。

?

DocFieldProcessorPerThread类完成第一步处理的核心方法就是processDocument()。在介绍这个方法之前,我们先来看看两个重要的类DocFieldProcessorPerField和FieldInfo

?

(1) DocFieldProcessorPerField类是一个合并了相同名字Field的类(可见下图黄色区域)。它是后面DocInverterPerField要处理的单位原料。源码如下:

final class DocFieldProcessorPerField {      final DocFieldConsumerPerField consumer;     //记录field的名字、是否要检索,是否要存储等信息     final FieldInfo fieldInfo;     //指向下一个DocFieldProcessorPerField的指针     DocFieldProcessorPerField next;     int lastGen = -1;     //包含相同名字的field的数量     int fieldCount;     //包含的相同名字的field     Fieldable[] fields = new Fieldable[1];    public DocFieldProcessorPerField(final DocFieldProcessorPerThread perThread, final FieldInfo fieldInfo) {         this.consumer = perThread.consumer.addField(fieldInfo);         this.fieldInfo = fieldInfo;    }    public void abort() {         consumer.abort();    }}
?

(2) FieldInfo类并不是指一个Field的全部信息,而是相同名字的Field合并之后的信息。合并过程重要通过update()方法将Field的其他不同属性统一起来(可见下图蓝色区域)。部分源码如下:

final class FieldInfo {    //Field的相同名字    String name;    //是否要索引    boolean isIndexed;    //编号    int number;    ....    //构造器    FieldInfo(..){    ...    }     //FieldInfo更新的准则是:    //原来的Field和新的Field有一个要索引(isIndexed=true),则更新后的也索引。    //如果新的Field不需要索引,则其他操作指标不变    //如果新的Field需要索引,则只要有一个操作指标为真,就更新后的也为真    void update(...){    ...    }}
?

下面我们重点看看processDocument()方法是如何把Document对象加工成DocFieldProcessorPerThread[]数组的。

final class DocFieldProcessorPerThread extends DocConsumerPerThread {//存储最后处理的结构:DocFieldProcessorPerField[]数组DocFieldProcessorPerField[] fields = new DocFieldProcessorPerField[1];int fieldCount;//以Field名字作为关键字的DocFieldProcessorPerField哈希表结构DocFieldProcessorPerField[] fieldHash = new DocFieldProcessorPerField[2];/** * 扩大DocFieldProcessorPerField的Hash表容量 * 每一次扩大到原来容量的2倍,并且将原来存储的DocFieldProcessorPerField对象顺序移动到Hash的最大位置处 * 比如:原来的容量为2,扩大之后的容量为4,将fieldHash[1]->fieldHash[3],fieldHash[0]->fieldHash[2] */private void rehash() {....}/** *第一加工车间处理核心流程 */public DocumentsWriter.DocWriter processDocument() {    //初始化各项数据    consumer.startDocument();    fieldsWriter.startDocument();    //要处理的document对象    final Document doc = docState.doc;    assert docFieldProcessor.docWriter.writer.testPoint("DocumentsWriter.ThreadState.init start");    //记录处理过程中生成的DocFieldProcessorPerField的数量    fieldCount = 0;    //当前的DocFieldProcessorPerField    final int thisFieldGen = fieldGen++;    final List<Fieldable> docFields = doc.getFields();    final int numDocFields = docFields.size();    for(int i=0;i<numDocFields;i++) {        //得到doc的每个Field        Fieldable field = docFields.get(i);        final String fieldName = field.name();        //以Field的名字为key,定位到fieldHash[]的位置号hashPos        final int hashPos = fieldName.hashCode() & hashMask;        //确定fieldHash[]上指定的hashPos位置是否已经有了数据,也就是是否产生冲突        DocFieldProcessorPerField fp = fieldHash[hashPos];        //如果Field的名字不同,但fieldHash[]的hashPos位置产生了Hash冲突,则采用Hash链表结构加入到冲突位置上的链表末尾。        while(fp != null && !fp.fieldInfo.name.equals(fieldName))             fp = fp.next;        //如果fieldHash[]的hashPos位置上没有数据,则将新的Field包装成DocFieldProcessorPerField对象加入到Hash表中        if (fp == null) {            FieldInfo fi = fieldInfos.add(fieldName, field.isIndexed(), field.isTermVectorStored(),field.isStorePositionWithTermVector(), field.isStoreOffsetWithTermVector(),field.getOmitNorms(), false, field.getOmitTermFreqAndPositions());            fp = new DocFieldProcessorPerField(this, fi);            fp.next = fieldHash[hashPos];            fieldHash[hashPos] = fp;            totalFieldCount++;            //如果DocFieldProcessorPerField的Hash表存储总数量已经尝过了总容量的1/2,则扩大容量            if (totalFieldCount >= fieldHash.length/2)                rehash();         }else{ //如果产生了冲突,并且冲突位置上的Field的名字与要加入的Field名字相同,则更新冲突位置上的FieldInfo             fp.fieldInfo.update(field.isIndexed(), field.isTermVectorStored(),                            field.isStorePositionWithTermVector(), field.isStoreOffsetWithTermVector(),                            field.getOmitNorms(), false, field.getOmitTermFreqAndPositions());         }                //如果具有相同名字的Field,则将同名的Field合并到同一个DocFieldProcessorPerField中的Fieldable[]中          if (thisFieldGen != fp.lastGen) {               fp.fieldCount = 0;               //如果fields[]已经存满,则扩大2倍的fields[]的容量               if (fieldCount == fields.length) {               final int newSize = fields.length*2;               DocFieldProcessorPerField newArray[] = new DocFieldProcessorPerField[newSize];               System.arraycopy(fields, 0, newArray, 0, fieldCount);               fields = newArray;           }                   fields[fieldCount++] = fp;           fp.lastGen = thisFieldGen;        }        //如果具有相同的Field名字,而DocFieldProcessorPerField中的Fieldable[]已经存满,则扩大2倍的此数组容量用于存放相同名字的Field        if (fp.fieldCount == fp.fields.length) {           Fieldable[] newArray = new Fieldable[fp.fields.length*2];           System.arraycopy(fp.fields, 0, newArray, 0, fp.fieldCount);           fp.fields = newArray;        }              fp.fields[fp.fieldCount++] = field;        if (field.isStored()) {            fieldsWriter.addField(field, fp.fieldInfo);        }   }   //将fields数组按field名字排序   quickSort(fields, 0, fieldCount-1);   //调用下一加工车间DocInverterPerField对每个DocFieldProcessorPerField对象进行处理   for(int i=0;i<fieldCount;i++)        fields[i].consumer.processFields(fields[i].fields, fields[i].fieldCount);    .......}
?

用个图例来说明一下DocFieldProcessorPerThread类所做的工作。我们拿《索引创建(1):IndexWriter索引器》1.1节前期工作中的doc1来作为DocFieldProcessorPerThread的原料。

?

原料:Document doc1 (为了说明相同Field的合并工作,我们加了一个相同名字,值不同的content Field)

????? Field name??????? Field value? isIndex? isStore???????? name??????????????? 1???? false???? true???????? path????? e:\\content\\1.txt???? false???? true??????? content The lucene is a good IR. I hope I can lean.???? true???? true??????? contentLucene 3.0 like a teacher. I love it.
???? true???? true

?

半成品: DocFieldProcessorPerField[] fields

【Lucene3.0 初窥】目录创建(2):DocumentWriter 处理流程一

注意,上图中的DocFieldProcessorPerField的next域都指向了null。其实,如果有Field1的名字name1与Field2的名字name2满足? HashCode(name1)=HashCode(name2) && !name1.equals(name2) 的情况下。Field2所构成的DocFieldProcessorPerField对象将加在Field1所构成的DocFieldProcessorPerField对象的next链表后面。这种组织方法便于我们在后面要讲到的建立倒排索引的处理。

?

总结:?DocFieldProcessorPerThread类的作用就是把Document对象加工成DocFieldProcessorPerField[](上图黄色区域) 。然后把每个DocFieldProcessorPerThread.Fieldable[](上图红色区域)

交给第二车间DocInverterPerField的processFields(《索引创建(3):DocmentWriter 处理流程二》)方法来完成了。

?

热点排行