?
上接《 索引創(chuàng)建(2):DocumentWriter處理流程一 》
?
1.3.2 第二車(chē)間——DocInverterPerField
?
DocInverterPerField 負(fù)責(zé)對(duì)DocFieldProcessorPerThread對(duì)象的Fieldable[]數(shù)組的內(nèi)容建立倒排索引,也就是處理同名字的所有Field。但實(shí)際上這個(gè)類(lèi)主要解決的是前期工作,比如分詞,統(tǒng)計(jì)位置信息等。倒排索引結(jié)構(gòu)的核心的工作由 TermsHashPerField 和 FreqProxTermsWriterPerField (第三車(chē)間 ) 來(lái)完成。這兩個(gè)類(lèi)將在后面的專(zhuān)題中再提及。
?
DocInverterPerField 核心方法是processFields(Fieldable[] fields)。它負(fù)責(zé)這幾個(gè)方面的工作:
(1)將field的value值切分成一個(gè)個(gè)term
(2)調(diào)用FieldInvertState類(lèi)來(lái)存儲(chǔ)和統(tǒng)計(jì)當(dāng)前field的所有term出現(xiàn)的位置position和offset信息,并計(jì)算該field的boost分值,為所有相同名字的fields的boost與文檔的boost的乘積。
(3) 調(diào)用 TermsHashPerField和 FreqProxTermsWriterPerField 把 每個(gè)term 加入倒排索引結(jié)構(gòu)。
?
?
Part I src code:
public void processFields(final Fieldable[] fields, final int count) { //FieldInvertState類(lèi)的職責(zé)就是跟蹤將要加入索引(index)結(jié)構(gòu)中的詞語(yǔ)的位置(position/offset) ?//首先初始化FieldInvertState類(lèi)的數(shù)據(jù)域。 fieldState.reset(docState.doc.getBoost()); //確定Field允許的最大詞語(yǔ)數(shù)量10000 final int maxFieldLength = docState.maxFieldLength; //確定fields數(shù)組是否需要索引(isIndexed) //如果有一個(gè)field需要索引,則doInvert=true final boolean doInvert = consumer.start(fields, count); //取出fields[]中的取出當(dāng)前field(這些field名字相同) for(int i=0;i<count;i++) { final Fieldable field = fields[i]; //當(dāng)前field需要索引且整個(gè)fields數(shù)組都需要檢索 if (field.isIndexed() && doInvert) { //如果有多個(gè)同名的field,則將后面的field的value接到前面的field之后 //即field[1]的第一個(gè)token的詞語(yǔ)位置要從field[0]開(kāi)始算起。 if (fieldState.length > 0) fieldState.position += docState.analyzer.getPositionIncrementGap(fieldInfo.name); //當(dāng)前field不需要分詞 if(!field.isTokenized()) { .... //則直接將整個(gè)field的值交給TermsHashPerField建立索引 consumer.start(field); try { consumer.add(); success = true; } finally { if (!success) docState.docWriter.setAborting(); .... }else {//當(dāng)前field需要分詞 final TokenStream stream; //確定field在創(chuàng)建的時(shí)候是否已經(jīng)有了一個(gè)內(nèi)容詞語(yǔ)的tokenStream final TokenStream streamValue = field.tokenStreamValue(); //field已經(jīng)有分好詞的tokenStream if (streamValue != null) stream = streamValue; else {//field沒(méi)有分好詞的tokenStream final Reader reader; //確定field的內(nèi)容是否是Reader類(lèi)型 final Reader readerValue = field.readerValue(); //field內(nèi)容是Reader類(lèi)型 if (readerValue != null) reader = readerValue; else { //filed內(nèi)容不是Reader類(lèi)型,則判斷是否是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; } //用分析器處理當(dāng)前field(進(jìn)行分詞和過(guò)濾),并加入到postingTable stream = docState.analyzer.reusableTokenStream(fieldInfo.name, reader); } ......第二部分源碼..... }//end if(需要索引) consumer.finish(); endConsumer.finish(); }//end for(每一個(gè)field) }//end processFields
?
第一部分源碼的主要作用就是根據(jù)每一個(gè)需要檢索的field的不同操作方式進(jìn)行處理。如果field不需要分詞,則直接將filed交給 TermsHashPerField建立索引結(jié)構(gòu)(code line: 30, 32)。如果field需要分詞,則首先判斷field的value是不是Reader類(lèi)型(分析器Analyzer只接受Reader類(lèi)型數(shù)據(jù)),不是則將value字符串值包裝成Reader類(lèi)型(code line:57)。再讓Analyzer分詞得到TokenStream stream(code line : 61)。然后將stream中的每一個(gè)token交給 TermsHashPerField建立索引結(jié)構(gòu)(請(qǐng)看后面的第二部分代碼)。
?
我們用上一節(jié)的doc1的例子來(lái)查看這個(gè)stream的結(jié)果,其中doc1通過(guò)上一節(jié)加工成了DocFieldProcessorPerThread fields[]數(shù)組。而fields[0]就是指doc1中名字為cotent的field集合,這個(gè)集合有兩個(gè)content field。
?
content field 1: The lucene is a good IR. I hope I can lean.
stream 的結(jié)果顯示(已經(jīng)去停用詞了):
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 的結(jié)果顯示(已經(jīng)去停用詞了):
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內(nèi)部指針指向第一個(gè)token stream.reset(); final int startLength = fieldState.length; try { //記錄當(dāng)前token首字母在文本中的位置,如果token是TokenStream中的第一個(gè)詞語(yǔ),則offsetEnd=-1 int offsetEnd = fieldState.offset-1; //獲取分詞后tokenStream的每一個(gè)token的全部信息 boolean hasMoreTokens = stream.incrementToken(); fieldState.attributeSource = stream; //得到當(dāng)前token的OffsetAttribute屬性信息 OffsetAttribute offsetAttribute =fieldState.attributeSource.addAttribute(OffsetAttribute.class); //得到當(dāng)前token的PositionIncrementAttribute屬性信息 PositionIncrementAttribute posIncrAttribute =fieldState.attributeSource.addAttribute(PositionIncrementAttribute.class); //利用TermsHashPerField將每一個(gè)token加入倒排索引結(jié)構(gòu) consumer.start(field); for(;;) { //tokenStream結(jié)束 if (!hasMoreTokens) break; //得到當(dāng)前token的positionIncreament屬性 final int posIncr = posIncrAttribute.getPositionIncrement(); //此時(shí)fieldState.position表示當(dāng)前token所在原文本中的詞語(yǔ)位置,即token前面有多少個(gè)詞語(yǔ) ?fieldState.position += posIncr; //positionIncreament屬性計(jì)算的時(shí)候就是相隔的詞語(yǔ)數(shù)量+1,因此統(tǒng)計(jì)當(dāng)前token前面的詞語(yǔ)數(shù)量的時(shí)候,要減1 if (fieldState.position > 0) { fieldState.position--; } if (posIncr == 0) fieldState.numOverlap++; try { //利用TermsHashPerField將當(dāng)前token以及fieldState當(dāng)前所記錄的位置信息一并加入進(jìn)倒排索引結(jié)構(gòu)中 consumer.add(); success = true; } finally { if (!success) docState.docWriter.setAborting(); } //準(zhǔn)備記錄下一個(gè)token,因此將當(dāng)前token算入進(jìn)去 fieldState.position++; //記錄當(dāng)前token的尾字母在原文本中所在的位置 ?offsetEnd = fieldState.offset + offsetAttribute.endOffset(); //fieldState.length記錄了當(dāng)前已經(jīng)處理了的token數(shù)量,如果超過(guò)了允許的最大數(shù)量,則后面的詞語(yǔ)將被丟棄,不再加入到索引中。 if (++fieldState.length >= maxFieldLength) { if (docState.infoStream != null) docState.infoStream.println("maxFieldLength " +maxFieldLength+ " reached for field " + fieldInfo.name + ", ignoring following tokens"); break; } //取下一個(gè)token hasMoreTokens = stream.incrementToken(); } stream.end(); } finally { stream.close(); }?
第二部分源碼的主要作用就是循環(huán)得到stream(第一部分代碼)中的每一個(gè)token(code line: 21),計(jì)算token在原始文本中的位置(code line: 28,31),并保存在fieldState.position和fieldState.offset中。同時(shí)token和fieldState中的統(tǒng)計(jì)信息交給 TermsHashPerField建立倒排索引結(jié)構(gòu)(code line: 38)。
?
總結(jié) ,下圖 展示了 DocInverterPerField 的作用。它會(huì)把不需要分詞的field以紅色方框的結(jié)構(gòu)(field value)傳給 TermsHashPerField 和 FreqProxTermsWriterPerField 來(lái)建立索引。而把需要分詞的content field變成一個(gè)個(gè)藍(lán)色方框的結(jié)構(gòu)(token && position)來(lái)建立索引,接下來(lái)就是對(duì)token建立倒排索引的過(guò)程了。請(qǐng)參見(jiàn)《 索引創(chuàng)建(4):DocumentWriter 處理流程三 》。
?
注意,上圖藍(lán)色方框的箭頭并不是指 DocInverterPerField會(huì)把他們建立成鏈表結(jié)構(gòu)。事實(shí)上,這些箭頭只是為了表明一個(gè)個(gè)token依次被 TermsHashPerField加入索引結(jié)構(gòu)的。另外,相同名字的field中的詞語(yǔ)會(huì)依次處理,就如同上面fields[0]和fields[1]。
?
更多文章、技術(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ì)您有幫助就好】元
