?
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 |
??????? content |
Lucene 3.0 like a teacher. I love it.
|
???? true | ???? true |
?
半成品:
DocFieldProcessorPerField[] fields
注意,上圖中的DocFieldProcessorPerField的next域都指向了null。其實,如果有Field1的名字name1與Field2的名字name2滿足? HashCode(name1)=HashCode(name2) && !name1.equals(name2) 的情況下。Field2所構成的DocFieldProcessorPerField對象將加在Field1所構成的DocFieldProcessorPerField對象的next鏈表后面。這種組織方法便于我們在后面要講到的建立倒排索引的處理。
?
總結: ? DocFieldProcessorPerThread 類的作用就是把Document對象加工成 DocFieldProcessorPerField [] (上圖黃色區域) 。然后把每個 DocField ProcessorPerThread .Fieldable[] (上圖紅色區域)
交給第二車間 DocInverterPerField 的 processFields ( 《索引創建(3):DocmentWriter 處理流程二》 )方法來完成了。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
