<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Lucene學(xué)習(xí)總結(jié)之三:Lucene的索引文件格式(2)

       

      四、具體格式

      上面曾經(jīng)交代過(guò),Lucene保存了從Index到Segment到Document到Field一直到Term的正向信息,也包括了從Term到Document映射的反向信息,還有其他一些Lucene特有的信息。下面對(duì)這三種信息一一介紹。

      4.1. 正向信息

      Index –> Segments (segments.gen, segments_N) –> Field(fnm, fdx, fdt) –> Term (tvx, tvd, tvf)

      上面的層次結(jié)構(gòu)不是十分的準(zhǔn)確,因?yàn)閟egments.gen和segments_N保存的是段(segment)的元數(shù)據(jù)信息(metadata),其實(shí)是每個(gè)Index一個(gè)的,而段的真正的數(shù)據(jù)信息,是保存在域(Field)和詞(Term)中的。

      4.1.1. 段的元數(shù)據(jù)信息(segments_N)

      一個(gè)索引(Index)可以同時(shí)存在多個(gè)segments_N(至于如何存在多個(gè)segments_N,在描述完詳細(xì)信息之后會(huì)舉例說(shuō)明),然而當(dāng)我們要打開一個(gè)索引的時(shí)候,我們必須要選擇一個(gè)來(lái)打開,那如何選擇哪個(gè)segments_N呢?

      Lucene采取以下過(guò)程:

      • 其一,在所有的segments_N中選擇N最大的一個(gè)。基本邏輯參照SegmentInfos.getCurrentSegmentGeneration(File[] files),其基本思路就是在所有以segments開頭,并且不是segments.gen的文件中,選擇N最大的一個(gè)作為genA。
      • 其二,打開segments.gen,其中保存了當(dāng)前的N值。其格式如下,讀出版本號(hào)(Version),然后再讀出兩個(gè)N,如果兩者相等,則作為genB。
      • segments.gen

        IndexInput genInput = directory.openInput(IndexFileNames.SEGMENTS_GEN);//"segments.gen"
        int version = genInput.readInt();//讀出版本號(hào)
        if (version == FORMAT_LOCKLESS) {//如果版本號(hào)正確
            long gen0 = genInput.readLong();//讀出第一個(gè)N
            long gen1 = genInput.readLong();//讀出第二個(gè)N
            if (gen0 == gen1) {//如果兩者相等則為genB
                genB = gen0;
            }
        }

      • 其三,在上述得到的genA和genB中選擇最大的那個(gè)作為當(dāng)前的N,方才打開segments_N文件。其基本邏輯如下:

        if (genA > genB)
            gen = genA;
        else
            gen = genB;

       

      如下圖是segments_N的具體格式:

      segments_N

      • Format:
        • 索引文件格式的版本號(hào)。
        • 由于Lucene是在不斷開發(fā)過(guò)程中的,因而不同版本的Lucene,其索引文件格式也不盡相同,于是規(guī)定一個(gè)版本號(hào)。
        • Lucene 2.1此值-3,Lucene 2.9時(shí),此值為-9。
        • 當(dāng)用某個(gè)版本號(hào)的IndexReader讀取另一個(gè)版本號(hào)生成的索引的時(shí)候,會(huì)因?yàn)榇酥挡煌鴪?bào)錯(cuò)。
      • Version:
        • 索引的版本號(hào),記錄了IndexWriter將修改提交到索引文件中的次數(shù)。
        • 其初始值大多數(shù)情況下從索引文件里面讀出,僅僅在索引開始創(chuàng)建的時(shí)候,被賦予當(dāng)前的時(shí)間,已取得一個(gè)唯一值。
        • 其值改變?cè)贗ndexWriter.commit->IndexWriter.startCommit->SegmentInfos.prepareCommit->SegmentInfos.write->writeLong(++version)
        • 其初始值之所最初取一個(gè)時(shí)間,是因?yàn)槲覀儾⒉魂P(guān)心IndexWriter將修改提交到索引的具體次數(shù),而更關(guān)心到底哪個(gè)是最新的。IndexReader中常比較自己的version和索引文件中的version是否相同來(lái)判斷此IndexReader被打開后,還有沒有被IndexWriter更新。

      //在DirectoryReader中有一下函數(shù)。

      public boolean isCurrent() throws CorruptIndexException, IOException {
        return SegmentInfos.readCurrentVersion(directory) == segmentInfos.getVersion();
      }

      • NameCount
        • 是下一個(gè)新段(Segment)的段名。
        • 所有屬于同一個(gè)段的索引文件都以段名作為文件名,一般為_0.xxx, _0.yyy,  _1.xxx, _1.yyy ……
        • 新生成的段的段名一般為原有最大段名加一。
        • 如同的索引,NameCount讀出來(lái)是2,說(shuō)明新的段為_2.xxx, _2.yyy

      image

      • SegCount
        • 段(Segment)的個(gè)數(shù)。
        • 如上圖,此值為2。
      • SegCount個(gè)段的元數(shù)據(jù)信息:
        • SegName
          • 段名,所有屬于同一個(gè)段的文件都有以段名作為文件名。
          • 如上圖,第一個(gè)段的段名為"_0",第二個(gè)段的段名為"_1"
        • SegSize
          • 此段中包含的文檔數(shù)
          • 然而此文檔數(shù)是包括已經(jīng)刪除,又沒有optimize的文檔的,因?yàn)樵趏ptimize之前,Lucene的段中包含了所有被索引過(guò)的文檔,而被刪除的文檔是保存在.del文件中的,在搜索的過(guò)程中,是先從段中讀到了被刪除的文檔,然后再用.del中的標(biāo)志,將這篇文檔過(guò)濾掉。
          • 如下的代碼形成了上圖的索引,可以看出索引了兩篇文檔形成了_0段,然后又刪除了其中一篇,形成了_0_1.del,又索引了兩篇文檔形成_1段,然后又刪除了其中一篇,形成_1_1.del。因而在兩個(gè)段中,此值都是2。

      IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED);
      writer.setUseCompoundFile(false);
      indexDocs(writer, docDir);//docDir中只有兩篇文檔

      //文檔一為:Students should be allowed to go out with their friends, but not allowed to drink beer.

      //文檔二為:My friend Jerry went to school to see his students but found them drunk which is not allowed.

      writer.commit();//提交兩篇文檔,形成_0段。

      writer.deleteDocuments(new Term("contents", "school"));//刪除文檔二
      writer.commit();//提交刪除,形成_0_1.del
      indexDocs(writer, docDir);//再次索引兩篇文檔,Lucene不能判別文檔與文檔的不同,因而算兩篇新的文檔。
      writer.commit();//提交兩篇文檔,形成_1段
      writer.deleteDocuments(new Term("contents", "school"));//刪除第二次添加的文檔二
      writer.close();//提交刪除,形成_1_1.del

      •  
        • DelGen
          • .del文件的版本號(hào)
          • Lucene中,在optimize之前,刪除的文檔是保存在.del文件中的。
          • 在Lucene 2.9中,文檔刪除有以下幾種方式:
            • IndexReader.deleteDocument(int docID)是用IndexReader按文檔號(hào)刪除。
            • IndexReader.deleteDocuments(Term term)是用IndexReader刪除包含此詞(Term)的文檔。
            • IndexWriter.deleteDocuments(Term term)是用IndexWriter刪除包含此詞(Term)的文檔。
            • IndexWriter.deleteDocuments(Term[] terms)是用IndexWriter刪除包含這些詞(Term)的文檔。
            • IndexWriter.deleteDocuments(Query query)是用IndexWriter刪除能滿足此查詢(Query)的文檔。
            • IndexWriter.deleteDocuments(Query[] queries)是用IndexWriter刪除能滿足這些查詢(Query)的文檔。
            • 原來(lái)的版本中Lucene的刪除一直是由IndexReader來(lái)完成的,在Lucene 2.9中雖可以用IndexWriter來(lái)刪除,但是其實(shí)真正的實(shí)現(xiàn)是在IndexWriter中,保存了readerpool,當(dāng)IndexWriter向索引文件提交刪除的時(shí)候,仍然是從readerpool中得到相應(yīng)的IndexReader,并用IndexReader來(lái)進(jìn)行刪除的。下面的代碼可以說(shuō)明:

      IndexWriter.applyDeletes()

      -> DocumentsWriter.applyDeletes(SegmentInfos)

           -> reader.deleteDocument(doc);

      •  
        •  
          •  
            • DelGen是每當(dāng)IndexWriter向索引文件中提交刪除操作的時(shí)候,加1,并生成新的.del文件。

      IndexWriter.commit()

      -> IndexWriter.applyDeletes()

          -> IndexWriter$ReaderPool.release(SegmentReader)

               -> SegmentReader(IndexReader).commit()

                   -> SegmentReader.doCommit(Map)

                        -> SegmentInfo.advanceDelGen()

                             -> if (delGen == NO) {
                                    delGen = YES;
                                 } else {
                                    delGen++;
                                 }

      IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED);
      writer.setUseCompoundFile(false);

      indexDocs(writer, docDir);//索引兩篇文檔,一篇包含"school",另一篇包含"beer"
      writer.commit();//提交兩篇文檔到索引文件,形成段(Segment) "_0"
      writer.deleteDocuments(new Term("contents", "school"));//刪除包含"school"的文檔,其實(shí)是刪除了兩篇文檔中的一篇。
      writer.commit();//提交刪除到索引文件,形成"_0_1.del"
      writer.deleteDocuments(new Term("contents", "beer"));//刪除包含"beer"的文檔,其實(shí)是刪除了兩篇文檔中的另一篇。
      writer.commit();//提交刪除到索引文件,形成"_0_2.del"
      indexDocs(writer, docDir);//索引兩篇文檔,和上次的文檔相同,但是Lucene無(wú)法區(qū)分,認(rèn)為是另外兩篇文檔。
      writer.commit();//提交兩篇文檔到索引文件,形成段"_1"
      writer.deleteDocuments(new Term("contents", "beer"));//刪除包含"beer"的文檔,其中段"_0"已經(jīng)無(wú)可刪除,段"_1"被刪除一篇。
      writer.close();//提交刪除到索引文件,形成"_1_1.del"

      形成的索引文件如下:

      image

       

      •  
        • DocStoreOffset
        • DocStoreSegment
        • DocStoreIsCompoundFile
          • 對(duì)于域(Stored Field)和詞向量(Term Vector)的存儲(chǔ)可以有不同的方式,即可以每個(gè)段(Segment)單獨(dú)存儲(chǔ)自己的域和詞向量信息,也可以多個(gè)段共享域和詞向量,把它們存儲(chǔ)到一個(gè)段中去。
          • 如果DocStoreOffset為-1,則此段單獨(dú)存儲(chǔ)自己的域和詞向量,從存儲(chǔ)文件上來(lái)看,如果此段段名為XXX,則此段有自己的XXX.fdt,XXX.fdx,XXX.tvf,XXX.tvd,XXX.tvx文件。DocStoreSegment和DocStoreIsCompoundFile在此處不被保存。
          • 如果DocStoreOffset不為-1,則DocStoreSegment保存了共享的段的名字,比如為YYY,DocStoreOffset則為此段的域及詞向量信息在共享段中的偏移量。則此段沒有自己的XXX.fdt,XXX.fdx,XXX.tvf,XXX.tvd,XXX.tvx文件,而是將信息存放在共享段的YYY.fdt,YYY.fdx,YYY.tvf,YYY.tvd,YYY.tvx文件中。
          • DocumentsWriter中有兩個(gè)成員變量:String segment是當(dāng)前索引信息存放的段,String docStoreSegment是域和詞向量信息存儲(chǔ)的段。兩者可以相同也可以不同,決定了域和詞向量信息是存儲(chǔ)在本段中,還是和其他的段共享。
          • IndexWriter.flush(boolean triggerMerge, boolean flushDocStores, boolean flushDeletes)中第二個(gè)參數(shù)flushDocStores會(huì)影響到是否單獨(dú)或是共享存儲(chǔ)。其實(shí)最終影響的是DocumentsWriter.closeDocStore()。每當(dāng)flushDocStores為false時(shí),closeDocStore不被調(diào)用,說(shuō)明下次添加到索引文件中的域和詞向量信息是同此次共享一個(gè)段的。直到flushDocStores為true的時(shí)候,closeDocStore被調(diào)用,從而下次添加到索引文件中的域和詞向量信息將被保存在一個(gè)新的段中,不同此次共享一個(gè)段(在這里需要指出的是Lucene的一個(gè)很奇怪的實(shí)現(xiàn),雖然下次域和詞向量信息是被保存到新的段中,然而段名卻是這次被確定了的,在initSegmentName中當(dāng)docStoreSegment == null時(shí),被置為當(dāng)前的segment,而非下一個(gè)新的segment,docStoreSegment = segment,于是會(huì)出現(xiàn)如下面的例子的現(xiàn)象)。
          • 好在共享域和詞向量存儲(chǔ)并不是經(jīng)常被使用到,實(shí)現(xiàn)也或有缺陷,暫且解釋到此。

            IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED);
            writer.setUseCompoundFile(false);

         
            indexDocs(writer, docDir);
            writer.flush();

      //flush生成segment "_0",并且flush函數(shù)中,flushDocStores設(shè)為false,也即下個(gè)段將同本段共享域和詞向量信息,這時(shí)DocumentsWriter中的docStoreSegment= "_0"。

            indexDocs(writer, docDir);
            writer.commit();

      //commit生成segment "_1",由于上次flushDocStores設(shè)為false,于是段"_1"的域以及詞向量信息是保存在"_0"中的,在這個(gè)時(shí)刻,段"_1"并不生成自己的"_1.fdx"和"_1.fdt"。然而在commit函數(shù)中,flushDocStores設(shè)為true,也即下個(gè)段將單獨(dú)使用新的段來(lái)存儲(chǔ)域和詞向量信息。然而這時(shí),DocumentsWriter中的docStoreSegment= "_1",也即當(dāng)段"_2"存儲(chǔ)其域和詞向量信息的時(shí)候,是存在"_1.fdx"和"_1.fdt"中的,而段"_1"的域和詞向量信息卻是存在"_0.fdt"和"_0.fdx"中的,這一點(diǎn)非常令人困惑。 如圖writer.commit的時(shí)候,_1.fdt和_1.fdx并沒有形成。

      image

            indexDocs(writer, docDir);
            writer.flush();

      //段"_2"形成,由于上次flushDocStores設(shè)為true,其域和詞向量信息是新創(chuàng)建一個(gè)段保存的,卻是保存在_1.fdt和_1.fdx中的,這時(shí)候才產(chǎn)生了此二文件。

      image

            indexDocs(writer, docDir);
            writer.flush();

      //段"_3"形成,由于上次flushDocStores設(shè)為false,其域和詞向量信息是共享一個(gè)段保存的,也是是保存在_1.fdt和_1.fdx中的

            indexDocs(writer, docDir);
            writer.commit();

      //段"_4"形成,由于上次flushDocStores設(shè)為false,其域和詞向量信息是共享一個(gè)段保存的,也是是保存在_1.fdt和_1.fdx中的。然而函數(shù)commit中flushDocStores設(shè)為true,也意味著下一個(gè)段將新創(chuàng)建一個(gè)段保存域和詞向量信息,此時(shí)DocumentsWriter中docStoreSegment= "_4",也表明了雖然段"_4"的域和詞向量信息保存在了段"_1"中,將來(lái)的域和詞向量信息卻要保存在段"_4"中。此時(shí)"_4.fdx"和"_4.fdt"尚未產(chǎn)生。   

      image

            indexDocs(writer, docDir);
            writer.flush();

      //段"_5"形成,由于上次flushDocStores設(shè)為true,其域和詞向量信息是新創(chuàng)建一個(gè)段保存的,卻是保存在_4.fdt和_4.fdx中的,這時(shí)候才產(chǎn)生了此二文件。

      image

            indexDocs(writer, docDir);
            writer.commit();
            writer.close();

      //段"_6"形成,由于上次flushDocStores設(shè)為false,其域和詞向量信息是共享一個(gè)段保存的,也是是保存在_4.fdt和_4.fdx中的

      image

      •  
        • HasSingleNormFile
          • 在搜索的過(guò)程中,標(biāo)準(zhǔn)化因子(Normalization Factor)會(huì)影響文檔最后的評(píng)分。
          • 不同的文檔重要性不同,不同的域重要性也不同。因而每個(gè)文檔的每個(gè)域都可以有自己的標(biāo)準(zhǔn)化因子。
          • 如果HasSingleNormFile為1,則所有的標(biāo)準(zhǔn)化因子都是存在.nrm文件中的。
          • 如果HasSingleNormFile不是1,則每個(gè)域都有自己的標(biāo)準(zhǔn)化因子文件.fN
        • NumField
          • 域的數(shù)量
        • NormGen
          • 如果每個(gè)域有自己的標(biāo)準(zhǔn)化因子文件,則此數(shù)組描述了每個(gè)標(biāo)準(zhǔn)化因子文件的版本號(hào),也即.fN的N。
        • IsCompoundFile
          • 是否保存為復(fù)合文件,也即把同一個(gè)段中的文件按照一定格式,保存在一個(gè)文件當(dāng)中,這樣可以減少每次打開文件的個(gè)數(shù)。
          • 是否為復(fù)合文件,由接口IndexWriter.setUseCompoundFile(boolean)設(shè)定。 
          • 非符合文件同符合文件的對(duì)比如下圖:
      非復(fù)合文件:
      image
      復(fù)合文件:
      image

       

      •  
        • DeletionCount
          • 記錄了此段中刪除的文檔的數(shù)目。
        • HasProx
          • 如果至少有一個(gè)段omitTf為false,也即詞頻(term freqency)需要被保存,則HasProx為1,否則為0。
        • Diagnostics
          • 調(diào)試信息。
      • User map data
        • 保存了用戶從字符串到字符串的映射Map
      • CheckSum
        • 此文件segment_N的校驗(yàn)和。

      讀取此文件格式參考SegmentInfos.read(Directory directory, String segmentFileName):

      • int format = input.readInt();
      • version = input.readLong(); // read version
      • counter = input.readInt(); // read counter
      • for (int i = input.readInt(); i > 0; i--) // read segmentInfos
        • add(new SegmentInfo(directory, format, input));
          • name = input.readString();
          • docCount = input.readInt();
          • delGen = input.readLong();
          • docStoreOffset = input.readInt();
          • docStoreSegment = input.readString();
          • docStoreIsCompoundFile = (1 == input.readByte());
          • hasSingleNormFile = (1 == input.readByte());
          • int numNormGen = input.readInt();
          • normGen = new long[numNormGen];
          • for(int j=0;j
          • normGen[j] = input.readLong();
        • isCompoundFile = input.readByte();
        • delCount = input.readInt();
        • hasProx = input.readByte() == 1;
        • diagnostics = input.readStringStringMap();
    3. userData = input.readStringStringMap();
    4. final long checksumNow = input.getChecksum();
    5. final long checksumThen = input.readLong();
    6.  

      4.1.2. 域(Field)的元數(shù)據(jù)信息(.fnm)

      一個(gè)段(Segment)包含多個(gè)域,每個(gè)域都有一些元數(shù)據(jù)信息,保存在.fnm文件中,.fnm文件的格式如下:

      fnm

      • FNMVersion
        • 是fnm文件的版本號(hào),對(duì)于Lucene 2.9為-2
      • FieldsCount
        • 域的數(shù)目
      • 一個(gè)數(shù)組的域(Fields)
        • FieldName:域名,如"title","modified","content"等。
        • FieldBits:一系列標(biāo)志位,表明對(duì)此域的索引方式
          • 最低位:1表示此域被索引,0則不被索引。所謂被索引,也即放到倒排表中去。
            • 僅僅被索引的域才能夠被搜到。
            • Field.Index.NO則表示不被索引。
            • Field.Index.ANALYZED則表示不但被索引,而且被分詞,比如索引"hello world"后,無(wú)論是搜"hello",還是搜"world"都能夠被搜到。
            • Field.Index.NOT_ANALYZED表示雖然被索引,但是不分詞,比如索引"hello world"后,僅當(dāng)搜"hello world"時(shí),能夠搜到,搜"hello"和搜"world"都搜不到。
            • 一個(gè)域出了能夠被索引,還能夠被存儲(chǔ),僅僅被存儲(chǔ)的域是搜索不到的,但是能通過(guò)文檔號(hào)查到,多用于不想被搜索到,但是在通過(guò)其它域能夠搜索到的情況下,能夠隨著文檔號(hào)返回給用戶的域。
            • Field.Store.Yes則表示存儲(chǔ)此域,F(xiàn)ield.Store.NO則表示不存儲(chǔ)此域。
          • 倒數(shù)第二位:1表示保存詞向量,0為不保存詞向量。
            • Field.TermVector.YES表示保存詞向量。
            • Field.TermVector.NO表示不保存詞向量。
          • 倒數(shù)第三位:1表示在詞向量中保存位置信息。
            • Field.TermVector.WITH_POSITIONS
          • 倒數(shù)第四位:1表示在詞向量中保存偏移量信息。
            • Field.TermVector.WITH_OFFSETS
          • 倒數(shù)第五位:1表示不保存標(biāo)準(zhǔn)化因子
            • Field.Index.ANALYZED_NO_NORMS
            • Field.Index.NOT_ANALYZED_NO_NORMS
          • 倒數(shù)第六位:是否保存payload

      要了解域的元數(shù)據(jù)信息,還要了解以下幾點(diǎn):

      • 位置(Position)和偏移量(Offset)的區(qū)別
        • 位置是基于詞Term的,偏移量是基于字母或漢字的。

      clip_image002

      • 索引域(Indexed)和存儲(chǔ)域(Stored)的區(qū)別
        • 一個(gè)域?yàn)槭裁磿?huì)被存儲(chǔ)(store)而不被索引(Index)呢?在一個(gè)文檔中的所有信息中,有這樣一部分信息,可能不想被索引從而可以搜索到,但是當(dāng)這個(gè)文檔由于其他的信息被搜索到時(shí),可以同其他信息一同返回。
        • 舉個(gè)例子,讀研究生時(shí),您好不容易寫了一篇論文交給您的導(dǎo)師,您的導(dǎo)師卻要他所第一作者而您做第二作者,然而您導(dǎo)師不想別人在論文系統(tǒng)中搜索您的名字時(shí)找到這篇論文,于是在論文系統(tǒng)中,把第二作者這個(gè)Field的Indexed設(shè)為false,這樣別人搜索您的名字,永遠(yuǎn)不知道您寫過(guò)這篇論文,只有在別人搜索您導(dǎo)師的名字從而找到您的文章時(shí),在一個(gè)角落表述著第二作者是您。
      • payload的使用
        • 我們知道,索引是以倒排表形式存儲(chǔ)的,對(duì)于每一個(gè)詞,都保存了包含這個(gè)詞的一個(gè)鏈表,當(dāng)然為了加快查詢速度,此鏈表多用跳躍表進(jìn)行存儲(chǔ)。
        • Payload信息就是存儲(chǔ)在倒排表中的,同文檔號(hào)一起存放,多用于存儲(chǔ)與每篇文檔相關(guān)的一些信息。當(dāng)然這部分信息也可以存儲(chǔ)域里(stored Field),兩者從功能上基本是一樣的,然而當(dāng)要存儲(chǔ)的信息很多的時(shí)候,存放在倒排表里,利用跳躍表,有利于大大提高搜索速度。
        • Payload的存儲(chǔ)方式如下圖:

      payload

      •  
        • Payload主要有以下幾種用法:
          • 存儲(chǔ)每個(gè)文檔都有的信息:比如有的時(shí)候,我們想給每個(gè)文檔賦一個(gè)我們自己的文檔號(hào),而不是用Lucene自己的文檔號(hào)。于是我們可以聲明一個(gè)特殊的域(Field)"_ID"和特殊的詞(Term)"_ID",使得每篇文檔都包含詞"_ID",于是在詞"_ID"的倒排表里面對(duì)于每篇文檔又有一項(xiàng),每一項(xiàng)都有一個(gè)payload,于是我們可以在payload里面保存我們自己的文檔號(hào)。每當(dāng)我們得到一個(gè)Lucene的文檔號(hào)的時(shí)候,就能從跳躍表中查找到我們自己的文檔號(hào)。
      //聲明一個(gè)特殊的域和特殊的詞

      public static final String ID_PAYLOAD_FIELD = "_ID";

      public static final String ID_PAYLOAD_TERM = "_ID";

      public static final Term ID_TERM = new Term(ID_PAYLOAD_TERM, ID_PAYLOAD_FIELD);

      //聲明一個(gè)特殊的TokenStream,它只生成一個(gè)詞(Term),就是那個(gè)特殊的詞,在特殊的域里面。

      static class SinglePayloadTokenStream extends TokenStream {
          private Token token;
          private boolean returnToken = false;

          SinglePayloadTokenStream(String idPayloadTerm) {
              char[] term = idPayloadTerm.toCharArray();
              token = new Token(term, 0, term.length, 0, term.length);
          }

          void setPayloadValue(byte[] value) {
              token.setPayload(new Payload(value));
              returnToken = true;
          }

          public Token next() throws IOException {
              if (returnToken) {
                  returnToken = false;
                  return token;
              } else {
                  return null;
              }
          }
      }

      //對(duì)于每一篇文檔,都讓它包含這個(gè)特殊的詞,在特殊的域里面

      SinglePayloadTokenStream singlePayloadTokenStream = new SinglePayloadTokenStream(ID_PAYLOAD_TERM);
      singlePayloadTokenStream.setPayloadValue(long2bytes(id));
      doc.add(new Field(ID_PAYLOAD_FIELD, singlePayloadTokenStream));

      //每當(dāng)?shù)玫揭粋€(gè)Lucene的文檔號(hào)時(shí),通過(guò)以下的方式得到payload里面的文檔號(hào)

      long id = 0;
      TermPositions tp = reader.termPositions(ID_PAYLOAD_TERM);
      boolean ret = tp.skipTo(docID);
      tp.nextPosition();
      int payloadlength = tp.getPayloadLength();
      byte[] payloadBuffer = new byte[payloadlength];
      tp.getPayload(payloadBuffer, 0);
      id = bytes2long(payloadBuffer);
      tp.close();

       

      •  
        •  
          • 影響詞的評(píng)分
            • 在Similarity抽象類中有函數(shù)public float scorePayload(byte [] payload, int offset, int length)  可以根據(jù)payload的值影響評(píng)分。
      • 讀取域元數(shù)據(jù)信息的代碼如下:

      FieldInfos.read(IndexInput, String)

      • int firstInt = input.readVInt();
      • size = input.readVInt();
      • for (int i = 0; i < size; i++)
        • String name = input.readString();
        • byte bits = input.readByte();
        • boolean isIndexed = (bits & IS_INDEXED) != 0;
        • boolean storeTermVector = (bits & STORE_TERMVECTOR) != 0;
        • boolean storePositionsWithTermVector = (bits & STORE_POSITIONS_WITH_TERMVECTOR) != 0;
        • boolean storeOffsetWithTermVector = (bits & STORE_OFFSET_WITH_TERMVECTOR) != 0;
        • boolean omitNorms = (bits & OMIT_NORMS) != 0;
        • boolean storePayloads = (bits & STORE_PAYLOADS) != 0;
        • boolean omitTermFreqAndPositions = (bits & OMIT_TERM_FREQ_AND_POSITIONS) != 0;

       

      4.1.3. 域(Field)的數(shù)據(jù)信息(.fdt,.fdx)

      fdxfdt

      • 域數(shù)據(jù)文件(fdt):
        • 真正保存存儲(chǔ)域(stored field)信息的是fdt文件
        • 在一個(gè)段(segment)中總共有segment size篇文檔,所以fdt文件中共有segment size個(gè)項(xiàng),每一項(xiàng)保存一篇文檔的域的信息
        • 對(duì)于每一篇文檔,一開始是一個(gè)fieldcount,也即此文檔包含的域的數(shù)目,接下來(lái)是fieldcount個(gè)項(xiàng),每一項(xiàng)保存一個(gè)域的信息。
        • 對(duì)于每一個(gè)域,fieldnum是域號(hào),接著是一個(gè)8位的byte,最低一位表示此域是否分詞(tokenized),倒數(shù)第二位表示此域是保存字符串?dāng)?shù)據(jù)還是二進(jìn)制數(shù)據(jù),倒數(shù)第三位表示此域是否被壓縮,再接下來(lái)就是存儲(chǔ)域的值,比如new Field("title", "lucene in action", Field.Store.Yes, …),則此處存放的就是"lucene in action"這個(gè)字符串。
      • 域索引文件(fdx)
        • 由域數(shù)據(jù)文件格式我們知道,每篇文檔包含的域的個(gè)數(shù),每個(gè)存儲(chǔ)域的值都是不一樣的,因而域數(shù)據(jù)文件中segment size篇文檔,每篇文檔占用的大小也是不一樣的,那么如何在fdt中辨別每一篇文檔的起始地址和終止地址呢,如何能夠更快的找到第n篇文檔的存儲(chǔ)域的信息呢?就是要借助域索引文件。
        • 域索引文件也總共有segment size個(gè)項(xiàng),每篇文檔都有一個(gè)項(xiàng),每一項(xiàng)都是一個(gè)long,大小固定,每一項(xiàng)都是對(duì)應(yīng)的文檔在fdt文件中的起始地址的偏移量,這樣如果我們想找到第n篇文檔的存儲(chǔ)域的信息,只要在fdx中找到第n項(xiàng),然后按照取出的long作為偏移量,就可以在fdt文件中找到對(duì)應(yīng)的存儲(chǔ)域的信息。
      • 讀取域數(shù)據(jù)信息的代碼如下:

      Document FieldsReader.doc(int n, FieldSelector fieldSelector)

      • long position = indexStream.readLong();//indexStream points to ".fdx"
      • fieldsStream.seek(position);//fieldsStream points to "fdt"
      • int numFields = fieldsStream.readVInt();
      • for (int i = 0; i < numFields; i++)
        • int fieldNumber = fieldsStream.readVInt();
        • byte bits = fieldsStream.readByte();
        • boolean compressed = (bits & FieldsWriter.FIELD_IS_COMPRESSED) != 0;
        • boolean tokenize = (bits & FieldsWriter.FIELD_IS_TOKENIZED) != 0;
        • boolean binary = (bits & FieldsWriter.FIELD_IS_BINARY) != 0;
        • if (binary)
          • int toRead = fieldsStream.readVInt();
          • final byte[] b = new byte[toRead];
          • fieldsStream.readBytes(b, 0, b.length);
          • if (compressed)
            • int toRead = fieldsStream.readVInt();
            • final byte[] b = new byte[toRead];
            • fieldsStream.readBytes(b, 0, b.length);
            • uncompress(b),
        • else
          • fieldsStream.readString()

      4.1.3. 詞向量(Term Vector)的數(shù)據(jù)信息(.tvx,.tvd,.tvf)

      termvector

      詞向量信息是從索引(index)到文檔(document)到域(field)到詞(term)的正向信息,有了詞向量信息,我們就可以得到一篇文檔包含那些詞的信息。

      • 詞向量索引文件(tvx)
        • 一個(gè)段(segment)包含N篇文檔,此文件就有N項(xiàng),每一項(xiàng)代表一篇文檔。
        • 每一項(xiàng)包含兩部分信息:第一部分是詞向量文檔文件(tvd)中此文檔的偏移量,第二部分是詞向量域文件(tvf)中此文檔的第一個(gè)域的偏移量。
      • 詞向量文檔文件(tvd)
        • 一個(gè)段(segment)包含N篇文檔,此文件就有N項(xiàng),每一項(xiàng)包含了此文檔的所有的域的信息。
        • 每一項(xiàng)首先是此文檔包含的域的個(gè)數(shù)NumFields,然后是一個(gè)NumFields大小的數(shù)組,數(shù)組的每一項(xiàng)是域號(hào)。然后是一個(gè)(NumFields - 1)大小的數(shù)組,由前面我們知道,每篇文檔的第一個(gè)域在tvf中的偏移量在tvx文件中保存,而其他(NumFields - 1)個(gè)域在tvf中的偏移量就是第一個(gè)域的偏移量加上這(NumFields - 1)個(gè)數(shù)組的每一項(xiàng)的值。
      • 詞向量域文件(tvf)
        • 此文件包含了此段中的所有的域,并不對(duì)文檔做區(qū)分,到底第幾個(gè)域到第幾個(gè)域是屬于那篇文檔,是由tvx中的第一個(gè)域的偏移量以及tvd中的(NumFields - 1)個(gè)域的偏移量來(lái)決定的。
        • 對(duì)于每一個(gè)域,首先是此域包含的詞的個(gè)數(shù)NumTerms,然后是一個(gè)8位的byte,最后一位是指定是否保存位置信息,倒數(shù)第二位是指定是否保存偏移量信息。然后是NumTerms個(gè)項(xiàng)的數(shù)組,每一項(xiàng)代表一個(gè)詞(Term),對(duì)于每一個(gè)詞,由詞的文本TermText,詞頻TermFreq(也即此詞在此文檔中出現(xiàn)的次數(shù)),詞的位置信息,詞的偏移量信息。
      • 讀取詞向量數(shù)據(jù)信息的代碼如下:

      TermVectorsReader.get(int docNum, String field, TermVectorMapper)

      • int fieldNumber = fieldInfos.fieldNumber(field);//通過(guò)field名字得到field號(hào)
      • seekTvx(docNum);//在tvx文件中按docNum文檔號(hào)找到相應(yīng)文檔的項(xiàng)
      • long tvdPosition = tvx.readLong();//找到tvd文件中相應(yīng)文檔的偏移量
      • tvd.seek(tvdPosition);//在tvd文件中按偏移量找到相應(yīng)文檔的項(xiàng)
      • int fieldCount = tvd.readVInt();//此文檔包含的域的個(gè)數(shù)。
      • for (int i = 0; i < fieldCount; i++) //按域號(hào)查找域
        • number = tvd.readVInt();
        • if (number == fieldNumber)
          • found = i;
      • position = tvx.readLong();//在tvx中讀出此文檔的第一個(gè)域在tvf中的偏移量
      • for (int i = 1; i <= found; i++)
        • position += tvd.readVLong();//加上所要找的域在tvf中的偏移量
      • tvf.seek(position);
      • int numTerms = tvf.readVInt();
      • byte bits = tvf.readByte();
      • storePositions = (bits & STORE_POSITIONS_WITH_TERMVECTOR) != 0;
      • storeOffsets = (bits & STORE_OFFSET_WITH_TERMVECTOR) != 0;
      • for (int i = 0; i < numTerms; i++)
        • start = tvf.readVInt();
        • deltaLength = tvf.readVInt();
        • totalLength = start + deltaLength;
        • tvf.readBytes(byteBuffer, start, deltaLength);
        • term = new String(byteBuffer, 0, totalLength, "UTF-8");
        • if (storePositions)
          • positions = new int[freq];
          • int prevPosition = 0;
          • for (int j = 0; j < freq; j++)
            • positions[j] = prevPosition + tvf.readVInt();
            • prevPosition = positions[j];
        • if (storeOffsets)
          • offsets = new TermVectorOffsetInfo[freq];
          • int prevOffset = 0;
          • for (int j = 0; j < freq; j++)
          • int startOffset = prevOffset + tvf.readVInt();
          • int endOffset = startOffset + tvf.readVInt();
          • offsets[j] = new TermVectorOffsetInfo(startOffset, endOffset);
          • prevOffset = endOffset;
      posted @ 2009-12-14 12:35  劉超覺先  閱讀(26842)  評(píng)論(4)    收藏  舉報(bào)
      主站蜘蛛池模板: 农村熟女大胆露脸自拍| 久久久久免费看成人影片| 亚洲精品毛片一区二区| 无码国模国产在线观看免费| 亚洲一区二区中文av| 免费A级毛片中文字幕| 日韩av熟女人妻一区二| 亚洲色大成网站WWW永久麻豆| 国产一区二区av天堂热| 国产精品天天看天天狠| 成人av亚洲男人色丁香| 综合偷自拍亚洲乱中文字幕| 少妇高潮水多太爽了动态图| 日本中文字幕在线播放| 另类国产精品一区二区| 日韩精品人妻黄色一级片| 国内自拍av在线免费| 日本一区二区三区四区黄色| 蜜桃精品成人影片| 亚洲一区二区三区激情在线| 中文字幕人妻无码一夲道| 亚洲色最新高清AV网站| 久久亚洲精品亚洲人av| www射我里面在线观看| 91久久国产成人免费观看| 精品无码日韩国产不卡av| 亚洲天码中文字幕第一页| 华人在线亚洲欧美精品| 狠狠婷婷色五月中文字幕| 久99久热这里只有精品| 色哟哟www网站入口成人学校| 亚洲全乱码精品一区二区| 日韩有码av中文字幕| 亚洲欧洲精品日韩av| 99热久久这里只有精品| 97se亚洲国产综合在线| 久久人与动人物a级毛片| 丁香婷婷综合激情五月色| 自拍偷自拍亚洲精品熟妇人| 午夜成人性爽爽免费视频| 亚洲欧美日韩愉拍自拍美利坚|