上接 《索引創(chuàng)建(1): IndexWriter索引器》
?
1.3?? 索引創(chuàng)建過(guò)程
?
DocumentsWriter是由IndexWriter調(diào)用來(lái)負(fù)責(zé)對(duì)多個(gè)document建立索引的核心類(lèi),但整個(gè)索引過(guò)程并不是由一個(gè)對(duì)象來(lái)完成的。而是有一系列的對(duì)象組成的處理鏈(IndexingChain)來(lái)完成的(這個(gè)過(guò)程就像流水線生產(chǎn)汽車(chē))。 下面是DocumentWriter開(kāi)始建立索引的源代碼。
//由IndexWriter調(diào)用的方法
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 {
//調(diào)用處理鏈的源頭DocFieldProcessorPerThread開(kāi)始對(duì)Document對(duì)象建立索引結(jié)構(gòu)
final DocWriter perDoc = state.consumer.processDocument();
finishDocument(state, perDoc);
success = true;
}
.......
}
?
1.3.1? 第一車(chē)間——DocFieldProcessorPerThread
?
DocFieldProcessorPerThread類(lèi)是索引創(chuàng)建處理鏈的第一步。其基本任務(wù):將document對(duì)象原料中所有相同名字的field合并成一個(gè) DocFieldProcessorPerThread對(duì)象 ,然后更新FieldInfo信息,最后對(duì)不同名字的Field構(gòu)成一個(gè) DocFieldProcessorPerThread[]對(duì)象數(shù)組。這個(gè)數(shù)組就是下一個(gè)車(chē)間DocInverterPerField要加工的原料了。
?
DocFieldProcessorPerThread類(lèi)完成第一步處理的核心方法就是processDocument()。在介紹這個(gè)方法之前,我們先來(lái)看看兩個(gè)重要的類(lèi)DocFieldProcessorPerField和FieldInfo
?
(1) DocFieldProcessorPerField 類(lèi)是一個(gè)合并了相同名字Field的類(lèi)(可見(jiàn)下圖黃色區(qū)域)。它是后面 DocInverterPerField 要處理的單位原料。源碼如下:
final class DocFieldProcessorPerField {
final DocFieldConsumerPerField consumer;
//記錄field的名字、是否要檢索,是否要存儲(chǔ)等信息
final FieldInfo fieldInfo;
//指向下一個(gè)DocFieldProcessorPerField的指針
DocFieldProcessorPerField next;
int lastGen = -1;
//包含相同名字的field的數(shù)量
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 類(lèi)并不是指一個(gè)Field的全部信息,而是相同名字的Field合并之后的信息。合并過(guò)程重要通過(guò)update()方法將Field的其他不同屬性統(tǒng)一起來(lái)(可見(jiàn)下圖藍(lán)色區(qū)域)。部分源碼如下:
final class FieldInfo {
//Field的相同名字
String name;
//是否要索引
boolean isIndexed;
//編號(hào)
int number;
....
//構(gòu)造器
FieldInfo(..){
...
}
//FieldInfo更新的準(zhǔn)則是:
//原來(lái)的Field和新的Field有一個(gè)要索引(isIndexed=true),則更新后的也索引。
//如果新的Field不需要索引,則其他操作指標(biāo)不變
//如果新的Field需要索引,則只要有一個(gè)操作指標(biāo)為真,就更新后的也為真
void update(...){
...
}
}
?
下面我們重點(diǎn)看看processDocument()方法是如何把Document對(duì)象加工成DocFieldProcessorPerThread[]數(shù)組的。
final class DocFieldProcessorPerThread extends DocConsumerPerThread {
//存儲(chǔ)最后處理的結(jié)構(gòu):DocFieldProcessorPerField[]數(shù)組
DocFieldProcessorPerField[] fields = new DocFieldProcessorPerField[1];
int fieldCount;
//以Field名字作為關(guān)鍵字的DocFieldProcessorPerField哈希表結(jié)構(gòu)
DocFieldProcessorPerField[] fieldHash = new DocFieldProcessorPerField[2];
/**
* 擴(kuò)大DocFieldProcessorPerField的Hash表容量
* 每一次擴(kuò)大到原來(lái)容量的2倍,并且將原來(lái)存儲(chǔ)的DocFieldProcessorPerField對(duì)象順序移動(dòng)到Hash的最大位置處
* 比如:原來(lái)的容量為2,擴(kuò)大之后的容量為4,將fieldHash[1]->fieldHash[3],fieldHash[0]->fieldHash[2]
*/
private void rehash() {
....
}
/**
*第一加工車(chē)間處理核心流程
*/
public DocumentsWriter.DocWriter processDocument() {
//初始化各項(xiàng)數(shù)據(jù)
consumer.startDocument();
fieldsWriter.startDocument();
//要處理的document對(duì)象
final Document doc = docState.doc;
assert docFieldProcessor.docWriter.writer.testPoint("DocumentsWriter.ThreadState.init start");
//記錄處理過(guò)程中生成的DocFieldProcessorPerField的數(shù)量
fieldCount = 0;
//當(dāng)前的DocFieldProcessorPerField
final int thisFieldGen = fieldGen++;
final List<Fieldable> docFields = doc.getFields();
final int numDocFields = docFields.size();
for(int i=0;i<numDocFields;i++) {
//得到doc的每個(gè)Field
Fieldable field = docFields.get(i);
final String fieldName = field.name();
//以Field的名字為key,定位到fieldHash[]的位置號(hào)hashPos
final int hashPos = fieldName.hashCode() & hashMask;
//確定fieldHash[]上指定的hashPos位置是否已經(jīng)有了數(shù)據(jù),也就是是否產(chǎn)生沖突
DocFieldProcessorPerField fp = fieldHash[hashPos];
//如果Field的名字不同,但fieldHash[]的hashPos位置產(chǎn)生了Hash沖突,則采用Hash鏈表結(jié)構(gòu)加入到?jīng)_突位置上的鏈表末尾。
while(fp != null && !fp.fieldInfo.name.equals(fieldName))
fp = fp.next;
//如果fieldHash[]的hashPos位置上沒(méi)有數(shù)據(jù),則將新的Field包裝成DocFieldProcessorPerField對(duì)象加入到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表存儲(chǔ)總數(shù)量已經(jīng)嘗過(guò)了總?cè)萘康?/2,則擴(kuò)大容量
if (totalFieldCount >= fieldHash.length/2)
rehash();
}else{ //如果產(chǎn)生了沖突,并且沖突位置上的Field的名字與要加入的Field名字相同,則更新沖突位置上的FieldInfo
fp.fieldInfo.update(field.isIndexed(), field.isTermVectorStored(),
field.isStorePositionWithTermVector(), field.isStoreOffsetWithTermVector(),
field.getOmitNorms(), false, field.getOmitTermFreqAndPositions());
}
//如果具有相同名字的Field,則將同名的Field合并到同一個(gè)DocFieldProcessorPerField中的Fieldable[]中
if (thisFieldGen != fp.lastGen) {
fp.fieldCount = 0;
//如果fields[]已經(jīng)存滿(mǎn),則擴(kuò)大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[]已經(jīng)存滿(mǎn),則擴(kuò)大2倍的此數(shù)組容量用于存放相同名字的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數(shù)組按field名字排序
quickSort(fields, 0, fieldCount-1);
//調(diào)用下一加工車(chē)間DocInverterPerField對(duì)每個(gè)DocFieldProcessorPerField對(duì)象進(jìn)行處理
for(int i=0;i<fieldCount;i++)
fields[i].consumer.processFields(fields[i].fields, fields[i].fieldCount);
.......
}
?
用個(gè)圖例來(lái)說(shuō)明一下DocFieldProcessorPerThread類(lèi)所做的工作。我們拿《 索引創(chuàng)建(1):IndexWriter索引器 》1.1節(jié)前期工作中的doc1來(lái)作為DocFieldProcessorPerThread的原料。
?
原料:Document doc1 (為了說(shuō)明相同F(xiàn)ield的合并工作,我們加了一個(gè)相同名字,值不同的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。其實(shí),如果有Field1的名字name1與Field2的名字name2滿(mǎn)足? HashCode(name1)=HashCode(name2) && !name1.equals(name2) 的情況下。Field2所構(gòu)成的DocFieldProcessorPerField對(duì)象將加在Field1所構(gòu)成的DocFieldProcessorPerField對(duì)象的next鏈表后面。這種組織方法便于我們?cè)诤竺嬉v到的建立倒排索引的處理。
?
總結(jié): ? DocFieldProcessorPerThread 類(lèi)的作用就是把Document對(duì)象加工成 DocFieldProcessorPerField [] (上圖黃色區(qū)域) 。然后把每個(gè) DocField ProcessorPerThread .Fieldable[] (上圖紅色區(qū)域)
交給第二車(chē)間 DocInverterPerField 的 processFields ( 《索引創(chuàng)建(3):DocmentWriter 處理流程二》 )方法來(lái)完成了。
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

