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。
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的具體格式:
- 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 { |
- NameCount
- 是下一個(gè)新段(Segment)的段名。
- 所有屬于同一個(gè)段的索引文件都以段名作為文件名,一般為_0.xxx, _0.yyy, _1.xxx, _1.yyy ……
- 新生成的段的段名一般為原有最大段名加一。
- 如同的索引,NameCount讀出來(lái)是2,說(shuō)明新的段為_2.xxx, _2.yyy
- 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。
- SegName
| IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED); //文檔一為: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"));//刪除文檔二 |
-
- 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ō)明:
- DelGen
| 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) { |
| IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR), new StandardAnalyzer(Version.LUCENE_CURRENT), true, IndexWriter.MaxFieldLength.LIMITED); indexDocs(writer, docDir);//索引兩篇文檔,一篇包含"school",另一篇包含"beer" 形成的索引文件如下: |
-
- 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); //flush生成segment "_0",并且flush函數(shù)中,flushDocStores設(shè)為false,也即下個(gè)段將同本段共享域和詞向量信息,這時(shí)DocumentsWriter中的docStoreSegment= "_0"。 indexDocs(writer, docDir); //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并沒有形成。 indexDocs(writer, docDir); //段"_2"形成,由于上次flushDocStores設(shè)為true,其域和詞向量信息是新創(chuàng)建一個(gè)段保存的,卻是保存在_1.fdt和_1.fdx中的,這時(shí)候才產(chǎn)生了此二文件。 indexDocs(writer, docDir); //段"_3"形成,由于上次flushDocStores設(shè)為false,其域和詞向量信息是共享一個(gè)段保存的,也是是保存在_1.fdt和_1.fdx中的 indexDocs(writer, docDir); //段"_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)生。 indexDocs(writer, docDir); //段"_5"形成,由于上次flushDocStores設(shè)為true,其域和詞向量信息是新創(chuàng)建一個(gè)段保存的,卻是保存在_4.fdt和_4.fdx中的,這時(shí)候才產(chǎn)生了此二文件。 indexDocs(writer, docDir); //段"_6"形成,由于上次flushDocStores設(shè)為false,其域和詞向量信息是共享一個(gè)段保存的,也是是保存在_4.fdt和_4.fdx中的 |
-
- 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ì)比如下圖:
- HasSingleNormFile
| 非復(fù)合文件: | 復(fù)合文件: |
-
- DeletionCount
- 記錄了此段中刪除的文檔的數(shù)目。
- HasProx
- 如果至少有一個(gè)段omitTf為false,也即詞頻(term freqency)需要被保存,則HasProx為1,否則為0。
- Diagnostics
- 調(diào)試信息。
- DeletionCount
- User map data
- 保存了用戶從字符串到字符串的映射Map
- CheckSum
- 此文件segment_N的校驗(yàn)和。
| 讀取此文件格式參考SegmentInfos.read(Directory directory, String segmentFileName):
|
4.1.2. 域(Field)的元數(shù)據(jù)信息(.fnm)
一個(gè)段(Segment)包含多個(gè)域,每個(gè)域都有一些元數(shù)據(jù)信息,保存在.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
- 最低位:1表示此域被索引,0則不被索引。所謂被索引,也即放到倒排表中去。
要了解域的元數(shù)據(jù)信息,還要了解以下幾點(diǎn):
- 位置(Position)和偏移量(Offset)的區(qū)別
- 位置是基于詞Term的,偏移量是基于字母或漢字的。
- 索引域(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主要有以下幾種用法:
- 存儲(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)。
- Payload主要有以下幾種用法:
| //聲明一個(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 { SinglePayloadTokenStream(String idPayloadTerm) { void setPayloadValue(byte[] value) { public Token next() throws IOException { //對(duì)于每一篇文檔,都讓它包含這個(gè)特殊的詞,在特殊的域里面 SinglePayloadTokenStream singlePayloadTokenStream = new SinglePayloadTokenStream(ID_PAYLOAD_TERM); long id = 0; |
-
-
- 影響詞的評(píng)分
- 在Similarity抽象類中有函數(shù)public float scorePayload(byte [] payload, int offset, int length) 可以根據(jù)payload的值影響評(píng)分。
- 影響詞的評(píng)分
-
- 讀取域元數(shù)據(jù)信息的代碼如下:
| FieldInfos.read(IndexInput, String)
|
4.1.3. 域(Field)的數(shù)據(jù)信息(.fdt,.fdx)
- 域數(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)
|
4.1.3. 詞向量(Term Vector)的數(shù)據(jù)信息(.tvx,.tvd,.tvf)
詞向量信息是從索引(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)
|

浙公網(wǎng)安備 33010602011771號(hào)