復(fù)合索引文件格式(.cfs)是如何產(chǎn)生的?從這個(gè)問題出發(fā),研究索引文件是如何合并的,這都是IndexWriter類中定義的一些重要的方法。
在建立索引過程中,生成的索引文件的格式有很多種。
在文章 Lucene-2.2.0 源代碼閱讀學(xué)習(xí)(4) 中測(cè)試的那個(gè)例子,沒有對(duì)IndexWriter進(jìn)行任何的客戶化設(shè)置,完全使用Lucene 2.2.0默認(rèn)的設(shè)置(以及,對(duì)Field的設(shè)置使用了Lucene自帶的Demo中的設(shè)置)。
運(yùn)行程序以后,在本地磁盤的索引目錄中生成了一些.擴(kuò)展名為.cfs的索引文件,即復(fù)合索引格式文件。如圖(該圖在文章 Lucene-2.2.0 源代碼閱讀學(xué)習(xí)(4) 中介紹過)所示:
從上面生成的那些.cfs復(fù)合索引文件可以看出,Lucene 2.2.0版本,IndexWriter索引的一個(gè)成員useCompoundFile的設(shè)置起了作用,可以在IndexWriter類的內(nèi)部看到定義和默認(rèn)設(shè)置:
private boolean useCompoundFile = true;
即,默認(rèn)使用復(fù)合索引文件格式來存儲(chǔ)索引文件。
在IndexWriter類的addDocument(Document doc, Analyzer analyzer)方法中可以看到,最后調(diào)用了 maybeFlushRamSegments()方法,這個(gè)方法的作用可是很大的,看它的定義:
protected final void
maybeFlushRamSegments
() throws CorruptIndexException, IOException {
??? if (ramSegmentInfos.size() >= minMergeDocs || numBufferedDeleteTerms >= maxBufferedDeleteTerms) {
?????
flushRamSegments();
??? }
}
這里,minMergeDocs是指:決定了合并索引段文件時(shí)指定的最小的Document的數(shù)量,在IndexWriter類中默認(rèn)值為10,可以在IndexWriter類中查看到:
???? private int minMergeDocs = DEFAULT_MAX_BUFFERED_DOCS;
?? public final static int DEFAULT_MAX_BUFFERED_DOCS = 10;
其中SegmentInfos ramSegmentInfos中保存了Document的數(shù)量的信息,如果Document的數(shù)量小于10,則調(diào)用flushRamSegments()方法進(jìn)行處理,flushRamSegments()方法的定義如下所示:
private final synchronized void flushRamSegments() throws CorruptIndexException, IOException {
???
flushRamSegments
(true);
}
在flushRamSegments()方法中又調(diào)用到了該方法的一個(gè)重載的方法,帶一個(gè)boolean型參數(shù)。該重載的方法定義如下:
protected final synchronized void flushRamSegments(boolean triggerMerge)
????? throws CorruptIndexException, IOException {
??? if (ramSegmentInfos.size() > 0 || bufferedDeleteTerms.size() > 0) {
?????
mergeSegments
(ramSegmentInfos, 0, ramSegmentInfos.size());
????? if (triggerMerge)
maybeMergeSegments
(minMergeDocs);
??? }
}
同樣,如果Document的數(shù)量小于10,則調(diào)用mergeSegments()方法,先看一下該方法的參數(shù):
private final int mergeSegments(SegmentInfos sourceSegments, int minSegment, int end)
第一個(gè)參數(shù)指定了一個(gè)SegmentInfos(上面調(diào)用傳遞了ramSegmentInfos) ;第二個(gè)參數(shù)是minSegment是最小的索引段數(shù)量(上面調(diào)用傳遞了0,說明如果存在>=0個(gè)索引段文件時(shí)就開始合并索引文件);第三個(gè)參數(shù)是end,指要合并索引段文件的個(gè)數(shù)(上面調(diào)用傳遞了ramSegmentInfos.size(),即對(duì)所有的索引段文件都執(zhí)行合并操作)。
繼續(xù)看mergeSegments()方法的實(shí)現(xiàn):
private final int mergeSegments(SegmentInfos sourceSegments, int minSegment, int end)
??? throws CorruptIndexException, IOException {
??? // doMerge決定了是否執(zhí)行合并操作,根據(jù)end的值,如果end為0說明要合并的索引段文件為0個(gè),即不需要合并,doMerge=false
??? boolean doMerge = end > 0;
/*??? 生成合并的索引段文件名稱,即根據(jù)SegmentInfos的counter值,如果counter=0,則返回的文件名為_0(沒有指定擴(kuò)展名)
??? final synchronized String newSegmentName() {
??????? return "_" + Integer.toString(segmentInfos.counter++, Character.MAX_RADIX);
??? }
*/
??? final String mergedName = newSegmentName();
??? SegmentMerger merger = null;??
// 聲明一個(gè)SegmentMerger變量
??? final List ramSegmentsToDelete = new ArrayList();??? // ramSegmentsToDelete列表用于存放可能要在合并結(jié)束后刪除的索引段文件,因?yàn)楹喜⒌倪^程中需要?jiǎng)h除掉合并完以后存在于內(nèi)存中的這些索引段文件
??? SegmentInfo newSegment = null;
??? int mergedDocCount = 0;
??? boolean anyDeletes = (bufferedDeleteTerms.size() != 0);
???
// This is try/finally to make sure merger's readers are closed:
??? try {
????? if (doMerge) {???
// 如果doMerge=true,即end>0,也就是說至少有1個(gè)以上的索引段文件存在,才能談得上合并
??????? if (infoStream != null) infoStream.print("merging segments");???
// infoStream是一個(gè)PrintStream輸出流對(duì)象,合并完成后要向索引目錄中寫入合并后的索引段文件,必須有一個(gè)打開的輸出流
??????? merger = new SegmentMerger(this, mergedName);???
// 構(gòu)造一個(gè)SegmentMerger對(duì)象,通過參數(shù):當(dāng)前的打開的索引器this和合并后的索引段名稱mergedName(形如_N,其中N為數(shù))關(guān)于SegmentMerger類會(huì)在后面文章學(xué)習(xí)
??????? for (int i = minSegment; i < end; i++) {????
// 循環(huán)遍歷,從SegmentInfos sourceSegments中迭代出每個(gè)SegmentInfo對(duì)象
????????? SegmentInfo si = sourceSegments.info(i);
????????? if (infoStream != null)
??????????? infoStream.print(" " + si.name + " (" + si.docCount + " docs)");???
// SegmentInfo si的name在索引目錄中是唯一的;這里打印出每個(gè) SegmentInfo si的名稱和在這個(gè)索引段文件中Document的數(shù)量
????????? IndexReader reader = SegmentReader.get(si, MERGE_READ_BUFFER_SIZE);
?? // 調(diào)用SegmentReader類的靜態(tài)方法get(),根據(jù)每個(gè)SegmentInfo si獲取一個(gè)索引輸入流對(duì)象;在IndexWriter類中定義了成員MERGE_READ_BUFFER_SIZE=4096
????????? merger.add(reader);???
//?? 將獲取到的SegmentReader reader加入到SegmentMerger merger中
????????? if (reader.directory() == this.ramDirectory) {???
// 如果SegmentReader
reader是當(dāng)前的索引目錄,與當(dāng)前的RAMDirectory ramDirectory是同一個(gè)索引目錄
??????????? ramSegmentsToDelete.add(si);???
// 將該SegmentInfo si加入到待刪除的列表ramSegmentsToDelete中
????????? }
??????? }
????? }
????? SegmentInfos rollback = null;
????? boolean success = false;
????? // This is try/finally to rollback our internal state
????? // if we hit exception when doing the merge:
????? try {
??????? if (doMerge) {????
// 如果doMerge=true
????????? mergedDocCount = merger.merge();???
// 通過SegmentMerger merger獲取需要合并的索引段文件數(shù)量
????????? if (infoStream != null) {???
// 打印出合并后的索引段文件的名稱,及其合并了索引段文件的數(shù)量
??????????? infoStream.println(" into "+mergedName+" ("+mergedDocCount+" docs)");
????????? }
????????? newSegment = new SegmentInfo(mergedName, mergedDocCount,
?????????????????????????????????????? directory, false, true);???
// 實(shí)例化一個(gè)SegmentInfo對(duì)象
??????? }
???????
??????? if (sourceSegments != ramSegmentInfos || anyDeletes) {
?????????
// 通過克隆,存儲(chǔ)一個(gè)用來回滾用的SegmentInfos實(shí)例,以防合并過程中發(fā)生異常
????????? rollback = (SegmentInfos) segmentInfos.clone();
??????? }
??????? if (doMerge) {??
// 如果doMerge=true
????????? if (sourceSegments == ramSegmentInfos) {??
// 如果傳進(jìn)來的sourceSegments和內(nèi)存中的ramSegmentInfos是同一個(gè)
??????????? segmentInfos.addElement(newSegment);???
// 將合并后的新的SegmentInfo newSegment加入到segmentInfos中進(jìn)行管理,以便之后再對(duì)其操作
????????? } else {
// 如果傳進(jìn)來的sourceSegments和內(nèi)存中的ramSegmentInfos不是同一個(gè)
??????????? for (int i = end-1; i > minSegment; i--)????
// 刪除舊的信息,同時(shí)添加新的信息
????????????? sourceSegments.remove(i);
??????????? segmentInfos.set(minSegment, newSegment);
????????? }
??????? }
??????? if (sourceSegments == ramSegmentInfos) {???
// 如果傳進(jìn)來的sourceSegments和內(nèi)存中的ramSegmentInfos是同一個(gè),因?yàn)閰?shù)設(shè)置的原因,可能需要?jiǎng)h除合并以后原來舊的索引段文件
????????? maybeApplyDeletes(doMerge);???
// 調(diào)用 maybeApplyDeletes()方法執(zhí)行合并后的刪除處理
????????? doAfterFlush();
??????? }
???????
??????? checkpoint();??
// 調(diào)用該方法 checkpoint()檢查,確認(rèn)并提交更新
??????? success = true;??? // 如果檢查沒有發(fā)現(xiàn)異常,則置success=true
????? } finally {
??????? if (success) {???
// 如果success
=true,表示提交成功,要清理內(nèi)存
????????? if (sourceSegments == ramSegmentInfos) {
??????????? ramSegmentInfos.removeAllElements();
????????? }
??????? } else {???
// 如果發(fā)生異常,則需要回滾操作
????????? if (sourceSegments == ramSegmentInfos && !anyDeletes) {
??????????? if (newSegment != null &&
??????????????? segmentInfos.size() > 0 &&
??????????????? segmentInfos.info(segmentInfos.size()-1) == newSegment) {
????????????? segmentInfos.remove(segmentInfos.size()-1);
??????????? }
????????? } else if (rollback != null) {
??????????? segmentInfos.clear();
??????????? segmentInfos.addAll(rollback);
????????? }
?????????
// Delete any partially created and now unreferenced files:
????????? deleter.refresh();
??????? }
????? }
??? } finally {
?????
// 關(guān)閉所有的輸入流(readers),嘗試刪除過時(shí)的廢棄文件
????? if (doMerge) merger.closeReaders();
??? }
???
// 刪除RAM中的索引段文件
??? deleter.deleteDirect(ramDirectory, ramSegmentsToDelete);
???
// 一個(gè)檢查點(diǎn),允許一個(gè)IndexFileDeleter deleter有機(jī)會(huì)在該時(shí)間點(diǎn)上去刪除文件
??? deleter.checkpoint(segmentInfos, autoCommit);
??? if (useCompoundFile && doMerge) {?? // 如果IndexWriter索引器設(shè)置了useCompoundFile=true
????? boolean success = false;
????? try {
??????? merger.createCompoundFile(mergedName + ".cfs");???
//?? 創(chuàng)建復(fù)合索引文件(.cfs),即_N.cfs文件
??????? newSegment.setUseCompoundFile(true);???
// 設(shè)置SegmentInfo newSegment為復(fù)合索引文件的信息
??????? checkpoint();?????
// 調(diào)用該方法 checkpoint()檢查,確認(rèn)并提交更新
??????? success = true;
????? } finally {???
// 如果檢查過程中發(fā)生異常,則回滾
??????? if (!success) {??
????????? newSegment.setUseCompoundFile(false);
????????? deleter.refresh();
??????? }
????? }
??????
?????
// 一個(gè)檢查點(diǎn),允許一個(gè)IndexFileDeleter deleter有機(jī)會(huì)在該時(shí)間點(diǎn)上去刪除文件
????? deleter.checkpoint(segmentInfos, autoCommit);
??? }
??? return mergedDocCount;
// 返回需合并的索引段文件數(shù)量
}
?
?
?
?
?
?
?
?
?
在不帶參數(shù)的flushRamSegments()方法中,調(diào)用了帶參數(shù)的flushRamSegments(boolean triggerMerge),也就是說,默認(rèn)情況下,Lucene指定triggerMerge=true,可以在不帶參數(shù)的flushRamSegments()方法中看到對(duì)該參數(shù)的設(shè)置:
private final synchronized void flushRamSegments() throws CorruptIndexException, IOException {
???
flushRamSegments(true);
}
所以,在帶參數(shù)的flushRamSegments(boolean triggerMerge)方法中,一定會(huì)執(zhí)行maybeMergeSegments()這個(gè)合并索引的方法,如下所示:
if (triggerMerge) maybeMergeSegments(minMergeDocs);
這里,傳遞的參數(shù)minMergeDocs=10(Lucene默認(rèn)值),那么就應(yīng)該有一個(gè)maxMergeDocs的成員與之對(duì)應(yīng),在Lucene 2.2.0版本中,在IndexWriter類中定義了該maxMergeDocs成員的默認(rèn)值:
??? private int
maxMergeDocs
= DEFAULT_MAX_MERGE_DOCS;
??? public final static int DEFAULT_MAX_MERGE_DOCS = Integer.MAX_VALUE;
??? public static final int?? MAX_VALUE = 0x7fffffff;
maxMergeDocs是合并的最大的Document的數(shù)量,定義為最大的Integer。
因?yàn)橐粋€(gè)索引目錄中的索引段文件的數(shù)量可能大于minMergeDocs=10,如果也要對(duì)所有的索引段文件進(jìn)行合并,則指定合并最小數(shù)量minMergeDocs的Docment是不能滿足要求的,即使用mergeSegments()方法。
因此,maybeMergeSegments()就能實(shí)現(xiàn)合并性能的改善,它的聲明就是需要一個(gè)起始的參數(shù),從而進(jìn)行增量地合并索引段文件。該方法的實(shí)現(xiàn)如下所示:
/** Incremental segment merger. */
private final void maybeMergeSegments(int startUpperBound) throws CorruptIndexException, IOException {
??? long lowerBound = -1;
??? long upperBound = startUpperBound;???
// 使用upperBound存放傳遞進(jìn)來的startUpperBound
??? while (upperBound < maxMergeDocs) {???
// 如果upperBound < maxMergeDocs,一般來說,這個(gè)應(yīng)該總成立的
????? int minSegment = segmentInfos.size();???
//?? 設(shè)置minSegment的值為當(dāng)前的SegmentInfos segmentInfos 的大小
????? int maxSegment = -1;
?????
// 查找能夠合并的索引段文件
????? while (--minSegment >= 0) {????
// 就是遍歷SegmentInfos segmentInfos中的每個(gè)SegmentInfo si
??????? SegmentInfo si = segmentInfos.info(minSegment);???
// 從索引位置號(hào)最大的開始往外取
??????? if (maxSegment == -1 && si.docCount > lowerBound && si.docCount <= upperBound) {???
// maxSegment == -1;同時(shí)滿足-1=lowerBound <(一個(gè)索引段文件中Dcoment的數(shù)量si.docCount)<=upperBound = startUpperBound
?????????
// start from the rightmost* segment whose doc count is in bounds
????????? maxSegment = minSegment;???
//?? 設(shè)置maxSegment的值為當(dāng)前SegmentInfos的大小
??????? } else if (si.docCount > upperBound) {
?????????
// 直到segment中Document的數(shù)量超過了上限upperBound,則退出循環(huán)
????????? break;
??????? }
????? }
??? // 該while循環(huán)只執(zhí)行了一次,執(zhí)行過程中,將maxSegment賦值為segmentInfos.size()-1
????? minSegment++;???
// 上面循環(huán)中一直執(zhí)行--minSegment,則到這里minSegment=-1,設(shè)置其值為0
????? maxSegment++;???
// 因?yàn)閙axSegment=segmentInfos.size()-1,則設(shè)置為maxSegment=segmentInfos.size()
????? int numSegments = maxSegment - minSegment;???
// numSegments = maxSegment - minSegment = segmentInfos.size()
??
????? if (numSegments < mergeFactor) {
???
/* mergeFactor是合并因子,IndexWriter的成員,默認(rèn)設(shè)置為10,mergeFactor的值越大,則內(nèi)存中駐留的Document就越多,向索引目錄中寫入segment的次數(shù)就越少,雖然占用內(nèi)存較多,但是速度應(yīng)該很快的。每向索引文件中加入mergeFactor=10個(gè)Document的時(shí)候,就會(huì)在索引目錄中生成一個(gè)索引段文件(segment) */
??????? break;???
// numSegments < mergeFactor則沒有達(dá)到合并所需要的數(shù)量,不需要合并,直接退出
????? } else {
??????? boolean exceedsUpperLimit = false;???
// 設(shè)置一個(gè)沒有超過上限的boolean型標(biāo)志(false)
???????
// 能夠合并的segments的數(shù)量>=mergeFactor時(shí)
??????? while (numSegments >= mergeFactor) {
?????????
// 調(diào)用mergeSegments(即上面的學(xué)習(xí)到的那個(gè)合并的方法)方法,
合并從minSegment開始的mergeFactor個(gè)segment
????????? int docCount = mergeSegments(segmentInfos, minSegment, minSegment + mergeFactor);
????????? numSegments -= mergeFactor;
// mergeFactor個(gè)segment已經(jīng)合并完成,剩下需要合并的數(shù)量要減去mergeFactor,在下一次循環(huán)的時(shí)候繼續(xù)合并
????????? if (docCount > upperBound) {???
// 如果上次合并返回的合并后的Document的數(shù)量大于上限
???????????
// 繼續(xù)在該層次合并剩余的segment
??????????? minSegment++;
??????????? exceedsUpperLimit = true;???
//?? 設(shè)置已經(jīng)超過上限,不能再進(jìn)行深一層次的的合并,即本輪合并就是最深層次的合并了
????????? } else {
// 如果上次合并返回的合并后的Document的數(shù)量沒有超過上限
???????????
// 考慮進(jìn)行更深層次的合并
??????????? numSegments++;
????????? }
??????? }
??????? if (!exceedsUpperLimit) {
// 如果上次合并返回的合并后的Document的數(shù)量大于上限,則終止執(zhí)行本層次合并
????????? break;
??????? }
????? }
????? lowerBound = upperBound;
????? upperBound *= mergeFactor;???
// 如果一個(gè)層次的合并成功后,還可以進(jìn)一步合并,則,上限變?yōu)樵瓉淼?0倍
??? }
}
合并索引段文件就是這樣實(shí)現(xiàn)的,并非只是在一個(gè)層次上合并:
第一層次合并時(shí),每次只能將10個(gè)segment索引段文件合并為1個(gè)新的segment,假設(shè)在這一層生成了500個(gè)經(jīng)過合并以后生成的索引段文件;
第二層次合并時(shí),每次能合并10*mergeFactor=10*10=100個(gè)segment,經(jīng)判斷,上一層次生成了500個(gè)segment還可以進(jìn)行第二層次的合并,現(xiàn)在每次100個(gè)segment文件才可能合并為1個(gè),可見,只能合并生成5個(gè)新的segment;
第三層次合并時(shí),每次能合并10*mergeFactor*mergeFactor=10*10*10=1000個(gè)segment,但是上一層次只是生成了5個(gè),不夠數(shù)量(1000個(gè)),不能繼續(xù)合并了,到此終止。
就是上面的那種原理,實(shí)現(xiàn)索引段文件的合并。如果希望進(jìn)行更深層次的合并,把mergeFactor的值設(shè)置的非常小就可以了,但是I/O操作過于頻繁,速度會(huì)很慢很慢的。
提高合并的速度,是以內(nèi)存空間開銷為代價(jià)的。
通過第一個(gè)合并的方法可以看出,只有當(dāng)為一個(gè)IndexWriter索引器設(shè)置了useCompoundFile=true的時(shí)候,才能生成復(fù)合索引文件_N.cfs,如下所示:
??? if (useCompoundFile && doMerge) {?? // 如果IndexWriter索引器設(shè)置了useCompoundFile=true
????? boolean success = false;
????? try {
??????? merger.createCompoundFile(mergedName + ".cfs");???
//?? 創(chuàng)建復(fù)合索引文件(.cfs),即_N.cfs文件
??????? newSegment.setUseCompoundFile(true);???
// 設(shè)置SegmentInfo newSegment為復(fù)合索引文件的信息
??????? checkpoint();?????
// 調(diào)用該方法 checkpoint()檢查,確認(rèn)并提交更新
??????? success = true;
????? } finally {???
// 如果檢查過程中發(fā)生異常,則回滾
??????? if (!success) {??
????????? newSegment.setUseCompoundFile(false);
????????? deleter.refresh();
??????? }
????? }
??????
?????
// 一個(gè)檢查點(diǎn),允許一個(gè)IndexFileDeleter deleter有機(jī)會(huì)在該時(shí)間點(diǎn)上去刪除文件
????? deleter.checkpoint(segmentInfos, autoCommit);
??? }
現(xiàn)在知道了,那些_N.cfs文件是合并的索引段文件。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

