Lucene學習總結之四:Lucene索引過程分析(3)

來源:互聯網
上載者:User
5、DocumentsWriter對CharBlockPool,ByteBlockPool,IntBlockPool的緩衝管理
  • 在索引的過程中,DocumentsWriter將詞資訊(term)儲存在CharBlockPool中,將文檔號(doc ID),詞頻(freq)和位置(prox)資訊儲存在ByteBlockPool中。
  • 在ByteBlockPool中,緩衝是分塊(slice)分配的,塊(slice)是分層次的,層次越高,此層的塊越大,每一層的塊大小事相同的。
    • nextLevelArray表示的是當前層的下一層是第幾層,可見第9層的下一層還是第9層,也就是說最高有9層。
    • levelSizeArray表示每一層的塊大小,第一層是5個byte,第二層是14個byte以此類推。

ByteBlockPool類中有以下靜態變數:

final static int[] nextLevelArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9};
final static int[] levelSizeArray = {5, 14, 20, 30, 40, 40, 80, 80, 120, 200};

  • 在ByteBlockPool中分配一個塊的代碼如下:

 

//此函數僅僅在upto已經是當前塊的結尾的時候方才調用來分配新塊。

public int allocSlice(final byte[] slice, final int upto) {

  //可根據塊的結束符來得到塊所在的層次。從而我們可以推斷,每個層次的塊都有不同的結束符,第1層為16,第2層位17,第3層18,依次類推。

  final int level = slice[upto] & 15;

  //從數組總得到下一個層次及下一層塊的大小。

  final int newLevel = nextLevelArray[level];

  final int newSize = levelSizeArray[newLevel];

  // 如果當前緩衝總量不夠大,則從DocumentsWriter的freeByteBlocks中分配。

  if (byteUpto > DocumentsWriter.BYTE_BLOCK_SIZE-newSize)

    nextBuffer();

  final int newUpto = byteUpto;

  final int offset = newUpto + byteOffset;

  byteUpto += newSize;

  //當分配了新的塊的時候,需要有一個指標從本塊指向下一個塊,使得讀取此資訊的時候,能夠在此塊讀取結束後,到下一個塊繼續讀取。

  //這個指標需要4個byte,在本塊中,除了結束符所佔用的一個byte之外,之前的三個byte的資料都應該移到新的塊中,從而四個byte連起來形成一個指標。

  buffer[newUpto] = slice[upto-3];

  buffer[newUpto+1] = slice[upto-2];

  buffer[newUpto+2] = slice[upto-1];

  // 將位移量(也即指標)寫入到連同結束符在內的四個byte

  slice[upto-3] = (byte) (offset >>> 24);

  slice[upto-2] = (byte) (offset >>> 16);

  slice[upto-1] = (byte) (offset >>> 8);

  slice[upto] = (byte) offset;

  // 在新的塊的結尾寫入新的結束符,結束符和層次的關係就是(endbyte = 16 | level)

  buffer[byteUpto-1] = (byte) (16|newLevel);

  return newUpto+3;

}

  • 在ByteBlockPool中,文檔號和詞頻(freq)資訊是應用或然跟隨原則寫到一個塊中去的,而位置資訊(prox)是寫入到另一個塊中去的,對於同一個詞,這兩塊的位移量儲存在IntBlockPool中。因而在IntBlockPool中,每一個詞都有兩個int,第0個表示docid + freq在ByteBlockPool中的位移量,第1個表示prox在ByteBlockPool中的位移量。
  • 在寫入docid + freq資訊的時候,調用termsHashPerField.writeVInt(0, p.lastDocCode),第一個參數表示向此詞的第0個位移量寫入;在寫入prox資訊的時候,調用termsHashPerField.writeVInt(1, (proxCode<<1)|1),第一個參數表示向此詞的第1個位移量寫入。
  • CharBlockPool是按照出現的先後順序儲存詞(term)
  • 在TermsHashPerField中,有一個成員變數RawPostingList[] postingsHash,為每一個term分配了一個RawPostingList,將上述三個緩衝關聯起來。

 

abstract class RawPostingList {

  final static int BYTES_SIZE = DocumentsWriter.OBJECT_HEADER_BYTES + 3*DocumentsWriter.INT_NUM_BYTE;

  int textStart; //此詞在CharBlockPool中的位移量,由此可以知道是哪個詞。

  int intStart; //此詞在IntBlockPool中的位移量,在指向的位置有兩個int,一個是docid + freq資訊的位移量,一個是prox資訊的位移量。

  int byteStart; //此詞在ByteBlockPool中的起始位移量

}

static final class PostingList extends RawPostingList {

  int docFreq;                                    // 此詞在此文檔中出現的次數

  int lastDocID;                                  // 上次處理完的包含此詞的文檔號。

  int lastDocCode;                                // 文檔號和詞頻按照或然跟隨原則形成的編碼

  int lastPosition;                               // 上次處理完的此詞的位置

}

這裡需要說明的是,在IntBlockPool中儲存了兩個在ByteBlockPool中的位移量,而在RawPostingList的byteStart又儲存了在ByteBlockPool中的位移量,這兩者有什麼區別呢?

在IntBlockPool中儲存的分別指向docid+freq及prox資訊在ByteBlockPool中的位移量是主要用來寫入資訊的,它記錄的位移量是下一個要寫入的docid+freq或者prox在ByteBlockPool中的位置,隨著資訊的不斷寫入,IntBlockPool中的兩個位移量是不斷改變的,始終指向下一個可以寫入的位置。

RawPostingList中byteStart主要是用來讀取docid及prox資訊的,當索引過程基本結束,所有的資訊都寫入在緩衝中了,那麼如何找到此詞對應的文檔號位移量及位置資訊,然後寫到索引檔案中去呢?自然是通過RawPostingList找到byteStart,然後根據byteStart在ByteBlockPool中找到docid+freq及prox資訊的起始位置,從起始位置開始的兩個大小為5的塊,第一個就是docid+freq資訊的源頭,第二個就是prox資訊的源頭,如果源頭的塊中包含了所有的資訊,讀出來就可以了,如果源頭的塊中有指標,則沿著指標尋找到下一個塊,從而可以找到所有的資訊。

  • 下面舉一個執行個體來表明如果進行緩衝管理的:

此例子中,準備添加三個檔案:

file01: common common common common common term

file02: common common common common common term term

file03: term term term common common common common common

file04: term

(1) 添加第一篇文檔第一個common

  • 在CharBlockPool中分配6個char來存放"common"字串
  • 在ByteBlockPool中分配兩個塊,每個塊大小為5,以16結束,第一個塊用來存放docid+freq資訊,第二個塊用來存放prox資訊。此時docid+freq資訊沒有寫入,docid+freq資訊總是在下一篇文檔的處理過程出現了"common"的時候方才寫入,因為當一篇文檔沒有處理完畢的時候,freq也即詞頻是無法知道的。而prox資訊存放0,是因為第一個common的位置為0,但是此0應該左移一位,最後一位置0表示沒有payload儲存,因而0<<1 + 0 = 0。
  • 在IntBlockPool中分配兩個int,一個指向第0個位置,是因為當前沒有docid+freq資訊寫入,第二個指向第6個位置,是因為第5個位置寫入了prox資訊。所以IntBlockPool中存放的是下一個要寫入的位置。

(2) 添加第四個common

  • 在ByteBlockPool中,prox資訊已經存放了4個,第一個0是代表第一個位置為0,後面不跟隨payload。第二個2表示,位置增量(差值原則)為1,後面不跟隨payload(或然跟隨原則),1<<1 + 0 =2。第三個第四個同第二個。

(3) 添加第五個common

  • ByteBlockPool中,存放prox資訊的塊已經全部填滿,必須重新分配新的塊。
  • 新的塊層次為2,大小為14,在緩衝的最後追加分配。
  • 原塊中連同結束位在內的四個byte作為指標(綠色部分),指向新的塊的其真實位址,在此為10.
  • 被指標佔用的結束位之前的三位移到新的塊中,也即6, 7, 8移到10, 11, 12處,13處是第五個common的prox資訊。
  • 指標的值並不都是四個byte的最後一位,當緩衝很大的時候,指標的值也會很大。比如指標出現[0, 0, 0, -56],最後一位為負,並不表示指向的負位置,而是最後一個byte的第一位為1,顯示為有符號數為負,-56的二進位是11001000,和前三個byte拼稱int,大小為200也即指向第200個位置。比如指標出現[0, 0, 1, 2],其轉換為二進位的int為100000010,大小為258,也即指向第258個位置。比如指標出現
    [0, 0, 1, -98],轉換為二進位的int為110011110,大小為414,也即指向第414個位置。

(4) 添加第一篇文檔,第一個term

  • CharBlockPool中分配了5個char來存放"term"
  • ByteBlockPool中分配了兩個塊來分別存放docid+freq資訊和prox資訊。第一個塊沒有資訊寫入,第二個塊寫入了"term"的位置資訊,即出現在第5個位置,並且後面沒有payload,5<<1 + 0 = 10。
  • IntBlockPool中分配了兩個int來指向"term"的兩個塊中下一個寫入的位置。

(5) 添加第二篇文檔第一個common

  • 第一篇文檔的common的docid+freq資訊寫入。在第一篇文檔中,"common"出現了5次,文檔號為0,按照或然跟隨原則,存放的資訊為[docid<<1 + 0, 5] = [0, 5],docid左移一位,最後一位為0表示freq大於1。
  • 第二篇文檔第一個common的位置資訊也寫入了,位置為0,0<<1 + 0 = 0。

(6) 添加第二篇文檔第一個term

  • 第一篇文檔中的term的docid+freq資訊寫入,在第一篇文檔中,"term"出現了1次,文檔號為0,所以儲存資訊為[docid<<1 + 1] = [1],文檔號左移一位,最後一位為1表示freq為1。
  • 第二篇文檔第一個term的prox資訊也寫入了,在第5個位置,5<<1 + 0 = 10。
  • 第二篇文檔中的5個common的prox資訊也寫入了,分別為從14到18的[0, 2, 2, 2, 2]

(7) 添加第三篇文檔的第一個term

  • 第二篇文檔的term的docid+freq資訊寫入,在第二篇文檔中,文檔號為1,"term"出現了2次,所以儲存為[docid<<1 + 0, freq] = [2, 2],儲存在25, 26兩個位置。
  • 第二篇文檔中兩個term的位置資訊也寫入了,為30, 31的[10, 2],也即出現在第5個,第6個位置,後面不跟隨payload。
  • 第三篇文檔的第一個term的位置資訊也寫入了,在第0個位置,不跟payload,為32儲存的[0]

(8) 添加第三篇文檔第二個term

  • term的位置資訊已經填滿了,必須分配新的塊,層次為2,大小為14,結束符為17,也即圖中34到47的位置。
  • 30到33的四個byte組成一個int指標,指向第34個位置
  • 原來30到32的三個prox資訊移到34到36的位置。
  • 在37處儲存第三篇文檔第二個term的位置資訊,位置為1,不跟隨payload,1<<1 + 0 = 2。

(9) 添加第三篇文檔第四個common

  • 第二篇文檔中"common"的docid+freq資訊寫入,文檔號為1,出現了5次,儲存為[docid << 1 + 0, freq],docid取差值為1,因而儲存為 [2, 5],即2,3的位置。
  • 第三篇文檔中前四個common的位置資訊寫入,即從19到22的[6, 2, 2, 2],即出現在第3個,第4個,第5個,第6個位置。
  • 第三篇文檔中的第三個"term"的位置資訊也寫入,為38處的[2]。

(10) 添加第三篇文檔的第五個common

  • 雖然common已經分配了層次為2,大小為14的第二個塊(從10到23),不過還是用完了,需要在緩衝的最後分配新的塊,層次為3,大小為20,結束符為18,也即從48到67的位置。
  • 從20到23的四個byte組成一個int指標指向新分配的塊。
  • 原來20到22的資料移到48至50的位置。
  • 第三篇文檔的第五個common的位置資訊寫入,為第51個位置的[2],也即緊跟上個common,後面沒有payload資訊。

(11) 添加第四篇文檔的第一個term

  • 寫入第三篇文檔的term的docid+freq資訊,文檔號為2,出現了三次,儲存為[docid<<1+0, freq],docid取差值為1,因而儲存為[2, 3]。
  • 然而儲存term的docid+freq資訊的塊已經滿了,需要在緩衝的最後追加新的塊,層次為2,大小為14,結束符為17,即從68到81的位置。
  • 從25到28的四個byte組成一個int指標指向新分配的塊。
  • 原來25到26的資訊移到68, 69處,在70, 71處寫入第三篇文檔的docid+freq資訊[2, 3]

(12) 最終PostingList, CharBlockPool, IntBlockPool,ByteBlockPool的關係如:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.