?
上接《 索引創建(2):DocumentWriter處理流程一 》
?
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:
public void processFields(final Fieldable[] fields, final int count) {
//FieldInvertState類的職責就是跟蹤將要加入索引(index)結構中的詞語的位置(position/offset)
?//首先初始化FieldInvertState類的數據域。
fieldState.reset(docState.doc.getBoost());
//確定Field允許的最大詞語數量10000
final int maxFieldLength = docState.maxFieldLength;
//確定fields數組是否需要索引(isIndexed)
//如果有一個field需要索引,則doInvert=true
final boolean doInvert = consumer.start(fields, count);
//取出fields[]中的取出當前field(這些field名字相同)
for(int i=0;i<count;i++) {
final Fieldable field = fields[i];
//當前field需要索引且整個fields數組都需要檢索
if (field.isIndexed() && doInvert) {
//如果有多個同名的field,則將后面的field的value接到前面的field之后
//即field[1]的第一個token的詞語位置要從field[0]開始算起。
if (fieldState.length > 0)
fieldState.position += docState.analyzer.getPositionIncrementGap(fieldInfo.name);
//當前field不需要分詞
if(!field.isTokenized()) {
....
//則直接將整個field的值交給TermsHashPerField建立索引
consumer.start(field);
try {
consumer.add();
success = true;
} finally {
if (!success)
docState.docWriter.setAborting();
....
}else {//當前field需要分詞
final TokenStream stream;
//確定field在創建的時候是否已經有了一個內容詞語的tokenStream
final TokenStream streamValue = field.tokenStreamValue();
//field已經有分好詞的tokenStream
if (streamValue != null)
stream = streamValue;
else {//field沒有分好詞的tokenStream
final Reader reader;
//確定field的內容是否是Reader類型
final Reader readerValue = field.readerValue();
//field內容是Reader類型
if (readerValue != null)
reader = readerValue;
else {
//filed內容不是Reader類型,則判斷是否是String
String stringValue = field.stringValue();
if (stringValue == null)
throw new IllegalArgumentException("field must have either TokenStream, String or Reader value");
perThread.stringReader.init(stringValue);
reader = perThread.stringReader;
}
//用分析器處理當前field(進行分詞和過濾),并加入到postingTable
stream = docState.analyzer.reusableTokenStream(fieldInfo.name, reader);
}
......第二部分源碼.....
}//end if(需要索引)
consumer.finish();
endConsumer.finish();
}//end for(每一個field)
}//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
|
offset | pos |
| 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:
...... 第一部分.....
// 將TokenStream內部指針指向第一個token
stream.reset();
final int startLength = fieldState.length;
try {
//記錄當前token首字母在文本中的位置,如果token是TokenStream中的第一個詞語,則offsetEnd=-1
int offsetEnd = fieldState.offset-1;
//獲取分詞后tokenStream的每一個token的全部信息
boolean hasMoreTokens = stream.incrementToken();
fieldState.attributeSource = stream;
//得到當前token的OffsetAttribute屬性信息
OffsetAttribute offsetAttribute =fieldState.attributeSource.addAttribute(OffsetAttribute.class);
//得到當前token的PositionIncrementAttribute屬性信息
PositionIncrementAttribute posIncrAttribute =fieldState.attributeSource.addAttribute(PositionIncrementAttribute.class);
//利用TermsHashPerField將每一個token加入倒排索引結構
consumer.start(field);
for(;;) {
//tokenStream結束
if (!hasMoreTokens) break;
//得到當前token的positionIncreament屬性
final int posIncr = posIncrAttribute.getPositionIncrement();
//此時fieldState.position表示當前token所在原文本中的詞語位置,即token前面有多少個詞語
?fieldState.position += posIncr;
//positionIncreament屬性計算的時候就是相隔的詞語數量+1,因此統計當前token前面的詞語數量的時候,要減1
if (fieldState.position > 0) {
fieldState.position--;
}
if (posIncr == 0)
fieldState.numOverlap++;
try {
//利用TermsHashPerField將當前token以及fieldState當前所記錄的位置信息一并加入進倒排索引結構中
consumer.add();
success = true;
} finally {
if (!success)
docState.docWriter.setAborting();
}
//準備記錄下一個token,因此將當前token算入進去
fieldState.position++;
//記錄當前token的尾字母在原文本中所在的位置
?offsetEnd = fieldState.offset + offsetAttribute.endOffset();
//fieldState.length記錄了當前已經處理了的token數量,如果超過了允許的最大數量,則后面的詞語將被丟棄,不再加入到索引中。
if (++fieldState.length >= maxFieldLength) {
if (docState.infoStream != null)
docState.infoStream.println("maxFieldLength " +maxFieldLength+ " reached for field " + fieldInfo.name + ", ignoring following tokens");
break;
}
//取下一個token
hasMoreTokens = stream.incrementToken();
}
stream.end();
} finally {
stream.close();
}
?
第二部分源碼的主要作用就是循環得到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]。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

