lucene .doc文件格式解析——見圖


摘自:http://forfuture1978.iteye.com/blog/546841

4.2.2. 文檔號及詞頻(frq)信息

 

文檔號及詞頻文件里面保存的是倒排表,是以跳躍表形式存在的。

  • 此文件包含TermCount個項,每一個詞都有一項,因為每一個詞都有自己的倒排表。
  • 對於每一個詞的倒排表都包括兩部分,一部分是倒排表本身,也即一個數組的文檔號及詞頻,另一部分是跳躍表,為了更快的訪問和定位倒排表中文檔號及詞頻的位置。
  • 對於文檔號和詞頻的存儲應用的是差值規則和或然跟隨規則,Lucene的文檔本身有以下幾句話,比較難以理解,在此解釋一下:

For example, the TermFreqs for a term which occurs once in document seven and three times in document eleven, with omitTf false, would be the following sequence of VInts:

15, 8, 3

If omitTf were true it would be this sequence of VInts instead:

7,4

首先我們看omitTf=false的情況,也即我們在索引中會存儲一個文檔中term出現的次數。

例子中說了,表示在文檔7中出現1次,並且又在文檔11中出現3次的文檔用以下序列表示:15,8,3.

那這三個數字是怎么計算出來的呢?

首先,根據定義TermFreq --> DocDelta[, Freq?],一個TermFreq結構是由一個DocDelta后面或許跟着Freq組成,也即上面我們說的A+B?結構。

DocDelta自然是想存儲包含此Term的文檔的ID號了,Freq是在此文檔中出現的次數。

所以根據例子,應該存儲的完整信息為[DocID = 7, Freq = 1] [DocID = 11,  Freq = 3](見全文檢索的基本原理章節)。

然而為了節省空間,Lucene對編號此類的數據都是用差值來表示的,也即上面說的規則2,Delta規則,於是文檔ID就不能按完整信息存了,就應該存放如下:

[DocIDDelta = 7, Freq = 1][DocIDDelta = 4 (11-7), Freq = 3]

然而Lucene對於A+B?這種或然跟隨的結果,有其特殊的存儲方式,見規則3,即A+B?規則,如果DocDelta后面跟隨的Freq為1,則用DocDelta最后一位置1表示。

如果DocDelta后面跟隨的Freq大於1,則DocDelta得最后一位置0,然后后面跟隨真正的值,從而對於第一個Term,由於Freq為1,於是放在DocDelta的最后一位表示,DocIDDelta = 7的二進制是000 0111,必須要左移一位,且最后一位置一,000 1111 = 15,對於第二個Term,由於Freq大於一,於是放在DocDelta的最后一位置零,DocIDDelta = 4的二進制是0000 0100,必須要左移一位,且最后一位置零,0000 1000 = 8,然后后面跟隨真正的Freq = 3。

於是得到序列:[DocDleta = 15][DocDelta = 8, Freq = 3],也即序列,15,8,3。

如果omitTf=true,也即我們不在索引中存儲一個文檔中Term出現的次數,則只存DocID就可以了,因而不存在A+B?規則的應用。

[DocID = 7][DocID = 11],然后應用規則2,Delta規則,於是得到序列[DocDelta = 7][DocDelta = 4 (11 - 7)],也即序列,7,4.

  • 對於跳躍表的存儲有以下幾點需要解釋一下:
    • 跳躍表可根據倒排表本身的長度(DocFreq)和跳躍的幅度(SkipInterval)而分不同的層次,層次數為NumSkipLevels = Min(MaxSkipLevels, floor(log(DocFreq/log(SkipInterval)))).
    • 第Level層的節點數為DocFreq/(SkipInterval^(Level + 1)),level從零計數。
    • 除了最高層之外,其他層都有SkipLevelLength來表示此層的二進制長度(而非節點的個數),方便讀取某一層的跳躍表到緩存里面。
    • 低層在前,高層在后,當讀完所有的低層后,剩下的就是最后一層,因而最后一層不需要SkipLevelLength。這也是為什么Lucene文檔中的格式描述為 NumSkipLevels-1, SkipLevel,也即低NumSKipLevels-1層有SkipLevelLength,最后一層只有SkipLevel,沒有SkipLevelLength。
    • 除最低層以外,其他層都有SkipChildLevelPointer來指向下一層相應的節點。
    • 每一個跳躍節點包含以下信息:文檔號,payload的長度,文檔號對應的倒排表中的節點在frq中的偏移量,文檔號對應的倒排表中的節點在prx中的偏移量。
    • 雖然Lucene的文檔中有以下的描述,然而實驗的結果卻不是完全准確的:

Example: SkipInterval = 4, MaxSkipLevels = 2, DocFreq = 35. Then skip level 0 has 8 SkipData entries, containing the 3rd, 7th, 11th, 15th, 19th, 23rd, 27th, and 31st document numbers in TermFreqs. Skip level 1 has 2 SkipData entries, containing the 15th and 31st document numbers in TermFreqs.

按照描述,當SkipInterval為4,且有35篇文檔的時候,Skip level = 0應該包括第3,第7,第11,第15,第19,第23,第27,第31篇文檔,Skip level = 1應該包括第15,第31篇文檔。

然而真正的實現中,跳躍表節點的時候,卻向前偏移了,偏移的原因在於下面的代碼:

  • FormatPostingsDocsWriter.addDoc(int docID, int termDocFreq)
    • final int delta = docID - lastDocID;
    • if ((++df % skipInterval) == 0)
      • skipListWriter.setSkipData(lastDocID, storePayloads, posWriter.lastPayloadLength);
      • skipListWriter.bufferSkip(df);

從代碼中,我們可以看出,當SkipInterval為4的時候,當docID = 0時,++df為1,1%4不為0,不是跳躍節點,當docID = 3時,++df=4,4%4為0,為跳躍節點,然而skipData里面保存的卻是lastDocID為2。

所以真正的倒排表和跳躍表中保存一下的信息:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM