欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

【Lucene3.0 初窺】索引創建(4):DocumentWrite

系統 1613 0

上接《 索引創建(3):DocumentWriter 處理流程二

?

1.3.3 第三車間 —— TermsHashPerField & FreqProxTermsWriterPerField

?

TermsHashPerField和FreqProxTermsWriterPerField負責將token信息(字符串內容termTest,所在文檔編號docID,所在文檔中的位置position,所在文檔中的詞頻frequence)添加到索引的Hash表結構(postingsHash)中。事實上,這些信息并不是直接存放在Hash表中的,而是存放在三個很重要的數據緩存池中 (CharBlockPool、IntBlockPool、ByteBlockPool) 。而postingsHash中存放的只是數據在三個數據池中的地址偏移。

?

★ TermsHashPerField

?

這里首先簡單介紹一下這三個數據池, 想要深刻了解這三個數據池, 可見《 索引數據池及內存數據細節 》。

1) CharBlockPool: 存儲token的字面信息

2) ByteBlockPool: 存儲token所在文檔編號,位置和詞頻信息

3) IntBlockPool: 存儲對應的ByteBlockPool中slice的位置


TermsHashPerField類的主要職責就是建立和維護一張postingsHash的倒排索引表。這張表是一張以token字符串作為關鍵字的Hash表。其結構如下:

    final class TermsHashPerField extends InvertedDocConsumerPerField {

    private int postingsHashSize = 4;
    //倒排索引表的Hash數組結構,初始大小為4
    private RawPostingList[] postingsHash = new RawPostingList[postingsHashSize];
}
  

其中postingHash[]的每個元素都是RawPostingList類型的。該類在內存中表示一個由token唯一標示的posting list(倒排索引結構:token-> posting list)。

    //該類在內存中表示一個由token唯一標示的posting list(倒排索引結構: token -> posting list)。
abstract class RawPostingList {
     int textStart;   //存儲token信息在CharBlockPool中的初始位置 
     int intStart;     //存儲token信息在IntBlockPool中的初始位置
   ? int byteStart;  //存儲token信息在ByteBlockPool中的初始位置
}
  

實際上postingHash[]的每個元素的實際類型是PostingList。這個類型包含的數據域如下:

    static final class PostingList extends RawPostingList {
    int docFreq;  //此詞在當前文檔中出現的次數
    int lastDocID;  // 上次處理完的包含此詞的文檔號。
    int lastDocCode;  // 文檔號和詞頻按照或然跟隨原則形成的編碼
    int lastPosition;  // 上次處理完的此詞的位置
}
  
?

?

TermsHashPerField.add()方法是創建待排索引的核心方法,我們分功能段來解析它的源代碼:

    // 將token加入到倒排索引的Hash鏈表結構中:token -> posting list
@Override
void add() throws IOException {....}
  

?

1、首先,計算token的hashcode值。 很顯然,下面的代碼表明處理token中的字符采用的是UTF-16編碼方式(Java對字符串都是Unicode編碼的)。想要了解UTF-16編碼方式,可參見《 解析Unicode編碼和Java char 》。另外,token的hashcode計算公式是code=(code*31)+ch,其中ch是token從末尾開始向前遍歷的字符。

    //得到當前token的內容
final char[] tokenText = termAtt.termBuffer();
//得到當前token的長度
final int tokenTextLen = termAtt.termLength();

int downto = tokenTextLen;
int code = 0;
//計算token的hashcode(其中每個字符按照的UTF-16編碼方式進行編碼)
while (downto > 0) {
    //從后往前取出token中的每一個字符
    char ch = tokenText[--downto];
    //判斷ch是不是Unicode編碼中的替代區域(surrogate area),這個區域不表示任何字符
    if (ch >= UnicodeUtil.UNI_SUR_LOW_START && ch <= UnicodeUtil.UNI_SUR_LOW_END) {
           if (0 == downto) {
               ch = tokenText[downto] = UnicodeUtil.UNI_REPLACEMENT_CHAR;
          } else {
              //取出連續兩個字符進行hashcode計算(UTF-16編碼方式)
               final char ch2 = tokenText[downto-1];
               if (ch2 >= UnicodeUtil.UNI_SUR_HIGH_START && ch2 <= UnicodeUtil.UNI_SUR_HIGH_END) {
                   code = ((code*31) + ch)*31+ch2;
                   downto--;
                   continue;
               } else {
                   ch = tokenText[downto] = UnicodeUtil.UNI_REPLACEMENT_CHAR;
               }            
          }
      } else if (ch >= UnicodeUtil.UNI_SUR_HIGH_START && (ch <= UnicodeUtil.UNI_SUR_HIGH_END ||
                                                          ch == 0xffff)) {
        ch = tokenText[downto] = UnicodeUtil.UNI_REPLACEMENT_CHAR;
      }
      //如果ch是正常字符,則開始計算token的hashcode
      code = (code*31) + ch;
}//end while
  

?

2、 其次,根據token的hashcode定位到postingHash[]上的位置。 我們通過code & postingHashMask來找到當前token在postingHash[]上的位置(其中postingsHashMask = postingsHashSize-1)。當產生Hash沖突的時候,其解決辦法就是不斷計算新的位置直到不產生沖突為止。

?

這里順便提一下:code & postingHashMask 和code % postingHashMask是完全等價的,而且位運算效率更高,Lucene經常使用位運算。

    //計算當前token將要插入在Hash表中的位置,code & postingsHashMask等價于code % postingHashMask
//postingsHashMask=postingsHashSize-1
int hashPos = code & postingsHashMask; 
//取出原hash表中hashPos位置上的數據
p = postingsHash[hashPos];
//如果原Hash表中的該位置上有數據并且兩個token的字符串內容不等,則產生Hash沖突
if (p != null && !postingEquals(tokenText, tokenTextLen)) {
    // 沖突解決方法:不斷計算新的hashcode,直到避開沖突位置
    final int inc = ((code>>8)+code)|1;
    do {
        code += inc;
        hashPos = code & postingsHashMask;
        p = postingsHash[hashPos];
     } while (p != null && !postingEquals(tokenText, tokenTextLen));
}
  
?

3、最后,將token的各種信息寫入數據池,并在PostingList中存儲數據池的地址偏移。 整個 過程有兩種可能:

?

(1) 如果當前token在前面從未遇到過,也就是已經加入索引結構的所有詞都沒有這個詞語。那么首先需要在postingHash中開辟一個新的PostingList準備存放當前token所對應信息(code line :18,33)。接著在CharBlackPool中創建一個新的區域來存儲當前token的字符串信息(line: 27),并且將地址偏移記錄在新創建的PostingList.textStart中(line: 25)。然后就是在ByteBlockPool中開辟兩個slice (line:57,58)。并且在IntBlockPool中開辟兩個空間存儲ByteBlockPool中的新開辟的slice地址偏移(line:57,60)。最后調用 FreqProxTermsWriterPerField.newTerm() 將token的docID+freq和position信息存儲進去(line: 67)。

?

(2) 如果當前token在前面已經遇到過了,此時就不需要在三大數據池中分配新的空間存放了。直接調用 FreqProxTermsWriterPerField.addTerm() 將信息存儲進去(line:72)。

    //說明當前token以前的文本中從未出現過
if (p == null) {
    final int textLen1 = 1+tokenTextLen;   
    //當CharBlockPool當前的buffer空間不足時,則重新分配一個新的buffer
    if (textLen1 + charPool.charUpto > DocumentsWriter.CHAR_BLOCK_SIZE) {
       if (textLen1 > DocumentsWriter.CHAR_BLOCK_SIZE) {
            if (docState.maxTermPrefix == null)
               docState.maxTermPrefix = new String(tokenText, 0, 30);
       consumer.skippingLongTerm();
       return;
       }  
       charPool.nextBuffer();
    }
      
    if (0 == perThread.freePostingsCount)
        perThread.morePostings();
    //從空閑的posting中分配新的posting
    p = perThread.freePostings[--perThread.freePostingsCount];
    assert p != null;

    //將當前token的內容寫入CharBlockPool中,此時text和CharBlockPool中的當前buffer都指向同一塊內存區域
    final char[] text = charPool.buffer;
    final int textUpto = charPool.charUpto;
    //PostingList類中的textStart保存的是當前token首字母在CharBlockPool中的位置
    p.textStart = textUpto + charPool.charOffset;
    charPool.charUpto += textLen1;
    System.arraycopy(tokenText, 0, text, textUpto, tokenTextLen);
    //每個詞當中存放一個分隔符'#66535'(66535號字符,我們用“#66535”表示)
    text[textUpto+tokenTextLen] = 0xffff;
          
    assert postingsHash[hashPos] == null;
    //將postingHash[hashPos]指向剛開辟的空閑RawPostList鏈表
    postingsHash[hashPos] = p;
    //記錄鏈表數量
    numPostings++;

    //如果postingsHash的加載因子達到了50%,則擴大2倍的postingsHash容量
    if (numPostings == postingsHashHalfSize)
        rehashPostings(2*postingsHashSize);

    //當IntBlockPool中buffer容量不足時,分配一個新buffer
    if (numPostingInt + intPool.intUpto > DocumentsWriter.INT_BLOCK_SIZE)
        intPool.nextBuffer();
    //當ByteBlockPool中buffer容量不足時,分配一個新buffer
    if (DocumentsWriter.BYTE_BLOCK_SIZE - bytePool.byteUpto < numPostingInt*ByteBlockPool.FIRST_LEVEL_SIZE)
        bytePool.nextBuffer();

    intUptos = intPool.buffer;
    intUptoStart = intPool.intUpto;
    //streamCount=2在ByteBlockPool對一個token同時開辟兩個大小一樣的slice,一個存放docID+frequence,另一個存放positive.
    //并且在IntBlockPool中也同時開辟兩個空間,用于分別存放一個token對應在ByteBlockPool中兩個slice的地址偏移
    intPool.intUpto += streamCount;
      
    //PostingList類中的intStart保存的是當前token在IntBlockPool中的兩個存儲空間的第一個地址
    p.intStart = intUptoStart + intPool.intOffset;
    //每一次記錄一個token,都需要在ByteBlckPool中開辟兩個塊來記錄: docID+freq(文檔ID+詞頻) 和 prox(位置)
    for(int i=0;i<streamCount;i++) {
        final int upto = bytePool.newSlice(ByteBlockPool.FIRST_LEVEL_SIZE);
       //IntBlockPool用來存儲ByteBlockPool每次開辟的塊的初始位置
       intUptos[intUptoStart+i] = upto + bytePool.byteOffset;
    }
    //PostingList類中的byteStart用于存儲當前token使用ByteBlckPool開辟的空間的初始位置
    //也就是剛開辟的兩個塊中第一個塊的初始位置
    p.byteStart = intUptos[intUptoStart];
      
    //token原來沒有出現過的時候,FreqProxTermsWriterPerField調用newTerm()記錄docID,freq和position
    consumer.newTerm(p);

} else { //如果此Token之前曾經出現過,FreqProxTermsWriterPerField調用addTerm()記錄docID,freq和position
      intUptos = intPool.buffers[p.intStart >> DocumentsWriter.INT_BLOCK_SHIFT];
      intUptoStart = p.intStart & DocumentsWriter.INT_BLOCK_MASK;
      consumer.addTerm(p);
}

  

???? 我們在《 全文檢索基本理論 》中講到的倒排索引結構是 token -> posting list的形式,而文檔結合的所有token組成了一個Dictionary。 TermsHashPerField類的作用就是建立這樣一個倒排索引結構——postingHash。其中 postingHash[](哈希數組)是以token的字面值作為關鍵字的,相當于Dictionary。而 postingHash的每一個元素都指向了PostingList對象,這個對象就是用來存儲指定token所對應的posting list信息(包括docID,freq,position)。實際上,真正的信息是存儲在三大數據池中的,但 PostingList對象只存儲三大數據池中的地址偏移。我們通過上面的代碼可以發現: TermsHashPerField已經把token的字面值存儲在CharBlockPool中了,并且在ByteBlockPool中分配好的存儲空間,并將地址偏移記錄到了IntBlockPool中了。接下來要做的就是把 token所對應的docID,freq,position的信息通過 FreqProxTermsWriterPerField 的方法寫入ByteBlockPool。

?

?

FreqProxTermsWriterPerField ?

?

(1)當出現一個索引結構中沒有的token時,我們需要調用 newTerm()方法將token所對應的 docID,freq,position信息存儲到ByteBlockPool中。

    final void newTerm(RawPostingList p0) {
    assert docState.testPoint("FreqProxTermsWriterPerField.newTerm start");
    FreqProxTermsWriter.PostingList p = (FreqProxTermsWriter.PostingList) p0;
    //當一個新的term出現的時候,包含此Term的就只有本篇文檔,記錄其ID 
    p.lastDocID = docState.docID;
    if (omitTermFreqAndPositions) {
        p.lastDocCode = docState.docID;
    } else {
        //docCode是文檔ID左移一位,為什么左移,這就需要Lucene的索引文件結構來解釋了。
        p.lastDocCode = docState.docID << 1;
        p.docFreq = 1;
       //寫入position信息到bytePool中,此時freq信息還不能寫入,因為當前的文檔還沒有處理完,尚不知道此文檔包含此token的總數。 
       writeProx(p, fieldState.position);
    }
}
  

?

(2) 當出現的token在索引結構中已經存在,我們需要調用 addTerm()方法將token所對應的 docID,freq,position信息存儲到 ByteBlockPool中。

    final void addTerm(RawPostingList p0) {
    assert docState.testPoint("FreqProxTermsWriterPerField.addTerm start");
    //取出postingHash已經存在的PostingList
    FreqProxTermsWriter.PostingList p = (FreqProxTermsWriter.PostingList) p0;

    assert omitTermFreqAndPositions || p.docFreq > 0;

    if (omitTermFreqAndPositions) {
      //p.lastDocID記錄了上一次token寫入索引結構的docID
      //docState.docID記錄的是token將要寫入索引結構的當前docID
      if (docState.docID != p.lastDocID) {
        assert docState.docID > p.lastDocID;
        termsHashPerField.writeVInt(0, p.lastDocCode);
        p.lastDocCode = docState.docID - p.lastDocID;
        p.lastDocID = docState.docID;
      }
    } else {
      if (docState.docID != p.lastDocID) {
        assert docState.docID > p.lastDocID;
        //如果當前的docID與上一次相同的token寫入的docID不同
        //則表明上一篇文本中該token已經處理完畢,則將freq信息ByteBlockPool中
        if (1 == p.docFreq)
          termsHashPerField.writeVInt(0, p.lastDocCode|1);
        else {
          termsHashPerField.writeVInt(0, p.lastDocCode);
          termsHashPerField.writeVInt(0, p.docFreq);
        }
        p.docFreq = 1;//對于新的文檔,freq還是為1. 
        p.lastDocCode = (docState.docID - p.lastDocID) << 1;//文檔號存儲差值 
        p.lastDocID = docState.docID;
        writeProx(p, fieldState.position);
      } else {
    	//當文檔ID不變的時候,說明此文檔中這個詞又出現了一次,從而freq加1,寫入再次出現的位置信息,用差值存儲。這里不寫入freq,因為該文檔還沒有結束
        p.docFreq++;
        //將position信息寫入ByteBlockPool中
        writeProx(p, fieldState.position-p.lastPosition);
      }
    }
}
  

?

上面newTerm()和addTerm()方法都需要調用writeProx()方法將position信息寫入ByteBlockPool中

    //將position信息寫入ByteBlockPool中
final void writeProx(FreqProxTermsWriter.PostingList p, int proxCode) {
    final Payload payload;
    //payloadAttribute是token的元數據信息
    if (payloadAttribute == null) {
      payload = null;
    } else {
      payload = payloadAttribute.getPayload();
    }
    
    //將position信息寫入ByteBlockPool中
    //其中writeVInt()第一個參數1表示將position寫入開辟在ByteBlockPool中兩個slice的第2個中
    //第一個slice存放docID+freq,第二個slice存放position
  ?if (payload != null && payload.length > 0) {
      termsHashPerField.writeVInt(1, (proxCode<<1)|1);
      termsHashPerField.writeVInt(1, payload.length);
      termsHashPerField.writeBytes(1, payload.data, payload.offset, payload.length);
      hasPayloads = true;      
    } else
      termsHashPerField.writeVInt(1, proxCode<<1);
    p.lastPosition = fieldState.position;
}
  

?

實際上,寫入ByteBlockPool中的數據到底是什么樣子的呢?還有在CharBlockPool中是如何存儲token的字面值的呢?IntBlockPool又是怎么樣記錄ByteBlockPool中的地址偏移的呢?這些問題我們將在《 索引數據池及其存儲細節 》詳細闡明.

?

?

總結:

?

???? 我們以前面所舉的例子為例,將《 索引數據池及內存數據細節 》中的content field的第一個token="lucene"(docID=0,position=1)創建索引結構,其圖示如下:

【Lucene3.0 初窺】索引創建(4):DocumentWriter 處理流程三


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 武松2013 | 三级色视频 | 日本久久综合网 | A片人人澡C片人人大片 | 日韩免费黄色片 | 日本高清在线精品一区二区三区 | 国产精品福利在线 | 精品国产18久久久久久二百 | 久久一日本道色综合久久 | 久久综合九色综合97婷婷群聊 | 国产精品一区二区在线 | 色偷偷综合 | 日韩在线第二页 | 久操网址 | 午夜看片在线观看 | chinese 军人 gay xx 呻吟 | 天天操天天爱天天干 | 久久穴 | 成人欧美一区二区三区视频xxx | 男女激情视频在线观看 | 日日摸夜夜摸人人嗷嗷叫 | 亚洲精品国产第一区二区多人 | 99av涩导航 | 性激情 | 澳门一级毛片免费播放 | 成在线人免费视频 | 中文字幕网在线 | 亚洲精品人成网在线播放影院 | 久久加久久 | 久久亚洲精品国产精品婷婷 | 午夜激情av| 欧美精品色 | 午夜性色一区二区三区不卡视频 | 亚洲 中文 欧美 日韩 在线观看 | 国产综合久久 | 成人免费网视频 | 久久艹一区 | 最新伦理片 | 青青青青久久久久国产的 | 青青草免费国产线观720 | 99热热热 |