?
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元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

