版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
1、lucene搜索實現(xiàn)過程詳解郭玉璞 2008-05-05一 文檔說明1 關(guān)于lucene索引生成過程和生成文件結(jié)構(gòu),在關(guān)于lucene建立索引的詳細過程及相關(guān)文件結(jié)構(gòu)一文中已經(jīng)說明,本文涉及到的文件結(jié)構(gòu),請參考該文檔,這里不再累贅,并且我們假設(shè)您已經(jīng)對索引文件格式有了一定的了解。2 為了對搜索的實現(xiàn)過程有一個清晰的認識,本文采用分級的形勢,層層深入。3 對于分詞這一塊,將有專題講解,本文在不影響理解的情況下,沒有詳解。二 搜索過程簡介索引的過程主要分為以下幾個步驟:1 打開索引文件,并將索引文件的相關(guān)信息讀入。代碼如下: IndexReader reader = IndexReader.Ope
2、n("index"); 其中,index為索引文件所在的文件夾名稱。該句執(zhí)行完以后,索引文件的相關(guān)信息將存放在reader中。2 實例化IndexSearcher,以便于后面的搜索操作。代碼如下:IndexSearcher searcher = new IndexSearcher(reader);該句執(zhí)行完后,索引文件信息將存放在searcher中,后面的的搜索將運用searcher,而不是運用reader。當然,初始化方式有很多種,這里就以代碼所示為例。3 聲明查詢分析器QueryPaser。代碼如下:QueryParser parser = new QueryParse
3、r(field, analyzer);其中,field為要查詢的字段,analyzer為所選擇的分析器。4 設(shè)置Query間的邏輯關(guān)系。代碼如下:parser.SetDefaultOperator(QueryParser.Operator.AND);該句的功能是,如果用戶以空格隔開兩個字符串,設(shè)置這兩個字符串之間的關(guān)系,代碼所示的是與的關(guān)系,根據(jù)需要也可以設(shè)置成OR等關(guān)系。此句可有可無,Lucene默認的是OR的關(guān)系。5 生成Query子對象。代碼如下:Query query = parser.Parse(strQuery);其中,strQuery為用戶輸入的待查詢的字符串。該句的主要功能是將
4、用戶輸入的字符串進行分詞,并記錄每個Token之間的位置關(guān)系,以便于后面的查詢。當然,Lucene允許用戶直接創(chuàng)建Query,也允許用戶采用多種方法構(gòu)建Query。例如:按詞條搜索TermQuery、“與或”搜索BooleanQuery、在某一范圍內(nèi)搜索RangeQuery、使用前綴搜索PrefixQuery、多關(guān)鍵字的搜索PhraseQuery、使用短語綴搜索PhrasePrefixQuery、相近詞語的搜索FuzzyQuery、使用通配符搜索WildcardQuery。各種方法原理相同,都是將各單個Query搜索,然后再將各自結(jié)果按照“與”或者“或”的關(guān)系得出最終結(jié)果。在本文中,我們將以代
5、碼所示的簡單方式為例。6 搜索,返回處理結(jié)果。代碼如下:Hits hits = searcher.Search(query);在該句中,不僅涉及搜索的過程,而且還有排序、過濾等過程。7根據(jù)搜索生成的內(nèi)部編號,返回真正的結(jié)果。代碼如下:Document doc = hits.Doc(i);三 搜索過程詳解。1IndexReader reader = IndexReader.Open("index");該句調(diào)用IndexReader的Open方法:public static IndexReader Open(System.String path)return Open(FSDi
6、rectory.GetDirectory(path, false), true);11 FSDirectory.GetDirectory(path,false),該方法獲取索引文件夾的完全路徑和創(chuàng)建臨時文件路徑。其中,path為索引文件所在文件夾的名稱,false表示不要創(chuàng)建新的文件夾。該方法調(diào)用FSDirectory的GetDirectory方法:public static FSDirectory GetDirectory(System.String path, bool create)return GetDirectory(new System.IO.FileInfo(path), cre
7、ate);111 GetDirectory(new System.IO.FileInfo(path), create);該方法是獲取文夾完整路徑的核心方法,其他方法都調(diào)用此方法。首先,通過語句file = new System.IO.FileInfo(file.FullName);獲得索引文件所在文件夾的完整路徑到file。其次,根據(jù)file創(chuàng)建臨時文件將要存放的路徑:FSDirectory dir;dir = (FSDirectory) DIRECTORIESfile;dir = (FSDirectory) System.Activator.CreateInstance(IMPL);然后,進
8、行初始化工作:dir.Init(file, create);由于現(xiàn)在是搜索過程,并非創(chuàng)建索引的過程,初始化工作只是將兩個完整路徑傳給dir。最后,返回dir。12 Open(FSDirectory.GetDirectory(path, false), true);該方法調(diào)用IndexReader的Open(Directory directory, bool closeDirectory)方法。其中,directroy就是上面返回的dir。在該方法中,有三個主要方法MakeLock、AnonymousClassWith、Run()。121 directory.MakeLock(IndexWrit
9、er.COMMIT_LOCK_NAME);其中COMMIT_LOCK_NAME為鎖文件名,在Lucene中為commit.lock。此刻該文件表示有進程在讀“segment”文件和打開某些段的文件。在該方法中,首先獲取索引文件目錄的前綴,并以此來命名鎖文件名稱;然后將臨時文件夾的完整路徑和鎖文件名稱組成完整了鎖文件名稱LockFile;最后初始化該鎖:AnonymousClassLock(lockFile, this);其中this為當前索引文件目錄路徑。122 AnonymousClassWith(directory, closeDirectory, directory.MakeLock(I
10、ndexWriter.COMMIT_LOCK_NAME), IndexWriter.COMMIT_LOCK_TIMEOUT);該方法主要做一些初始化工作。其中,COMMIT_LOCK_NAME為獲取鎖文件的限定時間,Lucene默認時間為10000毫秒。123 Run()這是讀取索引文件的核心方法。該方法代碼如下:public virtual System.Object Run()bool locked = false;trylocked = lock_Renamed.Obtain(lockWaitTimeout);return DoBody();finallyif (locked)lock_
11、Renamed.Release();1231 根據(jù)限定時間lockWaitTimeOut獲取鎖文件。在獲取過程中,每秒鐘試一次,直到獲得鎖文件為止。如果重試超過十次,則被阻塞。1232 DoBody();該方法首先聲明一個SegmentInfos對象,用以管理SegmentInfo:SegmentInfos infos = new SegmentInfos();12321 infos.Read(directory);其中directory為索引文件目錄。該方法讀取Segments文件信息。1)IndexInput input = directory.OpenInput(IndexFileNam
12、es.SEGMENTS);創(chuàng)建一個Segments文件的輸入流,用以讀取Segments文件信息。2)int format = input.ReadInt();讀取索引文件格式信息。Lucene2.0默認為1。3)version = input.ReadLong();讀取版本信息。4)counter = input.ReadInt();讀取counter,counter用于給新生成的索引起名字。5)在for循環(huán)中,int i = input.ReadInt();讀取segment的個數(shù)count。6)SegmentInfo si = new SegmentInfo(input.ReadStri
13、ng(), input.ReadInt(), directory);依次讀入每個segment的名字和包含文檔的個數(shù),并用Add(si);將每個si加入到ArrayList。當然,在舊版本的lucene當中,可能讀取的順序不太一樣,但道理是一樣的,都是根據(jù)索引的結(jié)構(gòu)依次讀出。12322 對于生成的索引,segment的個數(shù)可能不止一個,本文就以一個segment為例,多個segment與之類似。return SegmentReader.Get(infos, infos.Info(0), closeDirectory);讀取其他文件或為其他文件創(chuàng)建輸入流,以供以后讀取。Get方法層層調(diào)用,現(xiàn)在只
14、分析最后的核心方法:1)instance = (SegmentReader) System.Activator.CreateInstance(IMPL);創(chuàng)建SegmentReader實例instance。2)instance.Init(dir, sis, closeDir, ownDir);初始化工作。3)instance.Initialize(si);讀取的核心部分。首先要根據(jù).cfs文件是否存在來判斷是否是復合索引結(jié)構(gòu),本文采用非復合索引結(jié)構(gòu)來說明,復合索引結(jié)構(gòu)與之類似。a)fieldInfos = new FieldInfos(cfsDir, segment + ".fnm&
15、quot;);讀取.fnm文件信息。該方法實現(xiàn)如下: IndexInput input = d.OpenInput(name);name為.fnm文件名。為.fnm文件創(chuàng)建輸入流input,以供讀取。Read(input);讀取信息: int size = input.ReadVInt();讀取字段(filed)的個數(shù)。 System.String name = String.Intern(input.ReadString();讀取各個字段的名稱。 byte bits = input.ReadByte();讀取標志位。 下面五句是根據(jù)bits的值來判斷該域是否被索引等信息。 AddIntern
16、al(name, isIndexed, storeTermVector, storePositionsWithTermVector, storeOffsetWithTermVector, omitNorms);將該域的這些信息存儲,存儲的方法有兩種,一種是byName,根據(jù)名字來存儲;一種是byNumber,根據(jù)編號來存儲。Input.Close();關(guān)閉.fnm文件的輸入流。b)fieldsReader = new FieldsReader(cfsDir, segment, fieldInfos);為.fdt和.fdx文件創(chuàng)建輸入流fieldStream、indexStream,并記錄doc
17、ument的數(shù)量size。c)tis = new TermInfosReader(cfsDir, segment, fieldInfos);讀取.tis和.tii文件的相關(guān)信息,實現(xiàn)如下:origEnum = new SegmentTermEnum(directory.OpenInput(segment + ".tis"), fieldInfos, false);讀取.tis文件信息: directory.OpenInput(segment + ".tis");創(chuàng)建.tis文件的輸入流。 int firstInt = input.ReadInt();獲取
18、版本號。Lucene2.0當中為2。 size = input.ReadLong();讀取term的個數(shù)。 indexInterval = input.ReadInt();讀取索引間隔,默認值為128。 skipInterval = input.ReadInt();讀取跳躍間隔,默認值為16。indexEnum = new SegmentTermEnum(directory.OpenInput(segment + ".tii"), fieldInfos, true);讀取.tis文件信息。由于.tis文件結(jié)構(gòu)與.tii文件結(jié)構(gòu)相似,這里就不詳細展開敘述。d)if (HasD
19、eletions(si)deletedDocs = new BitVector(Directory(), segment + ".del");如果之前有刪除文檔的記錄,那么這里將刪除。關(guān)于document的刪除工作,將有專題講述,這里就不再敘述。e)freqStream = cfsDir.OpenInput(segment + ".frq");創(chuàng)建.frq文件的輸入流frqStream。f)proxStream = cfsDir.OpenInput(segment + ".prx");創(chuàng)建.prx文件的輸入流proxStream。g)
20、OpenNorms(cfsDir);讀取.f文件的相關(guān)信息。4)return instance;返回該實例。該實例記載了各個文件的信息。也就是說,關(guān)于索引各個文件的信息,記錄在searcher當中,后面的查詢工作將用到searcher。關(guān)于searcher的結(jié)構(gòu),請參看Lucene的源代碼。2IndexSearcher searcher = new IndexSearcher(reader);該方法主要是進行一些初始化工作。需要說明的是similarity,它是用于后面排序的積分計算的。這里調(diào)用similarity = Similarity.GetDefault();即采用Lucene默認的值
21、。3QueryParser parser = new QueryParser("describ", analyzer);聲明一個查詢分析器,并進行一些初始化的處理。該方法調(diào)用CharStream類型為參數(shù)的重載構(gòu)造函數(shù)public QueryParser(CharStream stream);然后初始化。31 public QueryParser(CharStream stream);該方法主要是利用一個空的CharStream類型來進行初始化工作,為下面的分詞工作做好準備。初始化過程中,涉及很多參數(shù),比如:fuzzyMinSim最小相似度,fuzzyPrefixLengt
22、h前綴長度,用于模糊查詢;一些以jj開頭的參數(shù),是由javaCC生成的,用于分詞的工作,這點在分析器的文檔中將詳細介紹,這里不再累贅。32 將傳入的分析器analyzer、字段field傳給對象:analyzer = a;ield = f; 至此,完成查詢分析器的初始化工作。4Query query = parser.Parse(strQuery);這是對用戶輸入字符串處理分詞和記錄位置關(guān)系的最核心部分。在該方法中,主要只有兩句。41 ReInit(new FastCharStream(new System.IO.StringReader(query);411該方法首先用StringReade
23、r對象構(gòu)造一個FastCharStream對象作為重新初始化(因為在聲明QueryPaser對象的時候已經(jīng)初始化過)的參數(shù)。即:new FastCharStream(new System.IO.StringReader(query);412 調(diào)用QueryPaser的方法ReInit(CharStream stream);其中stream就是上面生成的FastCharStream對象,F(xiàn)astCharStream類是CharStream類的子類。該方法和聲明查詢分析器時的構(gòu)造方法相似,這里就不再說明。42 return Query(field);其中field為要查詢的字段名。該方法比較復雜,
24、但我們只要把握一點,就是它的主要功能是對用戶輸入內(nèi)容進行分詞,然后構(gòu)建Query對象用于查詢,也就很好理解了。421 mods = Modifiers();該方法主要是判斷用戶是否使用復合查詢,即是否使用BooleanQuery等查詢。本文為了簡單起見,就以簡單查詢?yōu)槔瑥秃喜樵兊姆椒ㄊ穷愃频?,這點我們在前面已經(jīng)講過了。4211 Jj_ntk();這個是javaCC生成的方法,用于判斷得出mods的值。mods的默認值是0,即簡單查詢。return (jj_ntk = (token.next = token_source.GetNextToken().kind);或者return (jj_nt
25、k = jj_nt.kind);由此可知,該方法返回的是字符串的類型。4212 switch (jj_ntk = - 1) ? Jj_ntk() : jj_ntk),這個switch語句就是根據(jù)jj_ntk的值,來判定查詢方法。由代碼我們可以看出,主要由三種方法:PLUS、MINUS、NOT。由于我們以簡單方法為例,ret的值并沒有改變,返回默認值0。422 q = Clause(field);該方法實現(xiàn)分詞功能。4221 Jj_2_1(2);javaCC生成的方法,判斷是否為術(shù)語或者冒號。4222 這里同樣有一個switch (jj_ntk = - 1) ? Jj_ntk() : jj_nt
26、k)語句,不過它是用來判斷字符類型的,根據(jù)不同的類型,進行不同的處理。分詞涉及的類型很多,您可以從QueryParserConstants清楚地看出來。 查詢的分詞和索引時的分詞是類似的。關(guān)于分詞,我們將在分析器的專題中詳細介紹,這里就不再累贅。最終返回的q便為分詞的結(jié)果。它是Query類型,記錄了每個Token的位置position、所屬字段field、乘積因子boost、跨度slop和內(nèi)容content等詳細信息。423 AddClause(clauses, CONJ_NONE, mods, q);把每個Token加入到ArrayList的對象當中。在這個方法中,涉及到幾個判斷。4231
27、if (clauses.Count > 0 && conj = CONJ_AND),這是對于復合查詢而言的,如果之前已有Token加入ArrayList對象中,并且之前與現(xiàn)在是與的關(guān)系,那么將現(xiàn)在的Token加入其中,并設(shè)置與的關(guān)系。由于這里是簡單查詢,不作詳細說明。4232 if (clauses.Count > 0 && operator_Renamed = AND_OPERATOR && conj = CONJ_OR),這也是對復合查詢而言的。4233 if (operator_Renamed = OR_OPERATOR),用于
28、判斷當前各個Token之間的邏輯關(guān)系。下面將根據(jù)required和prohibited兩個參數(shù)的值,決定每個Token的與、或、非,然后將各個Token添加到ArrayList的對象clauses。4234 接下來的一個while循環(huán),根據(jù)之前判定的查詢方式、token間邏輯關(guān)系等參數(shù),返回Query對象。在簡單查詢中,直接返回對象q。5Hits hits = searcher.Search(query);這是整個搜索過程的核心部分,前面的部分都是為該部分作準備。現(xiàn)在,索引文件信息存儲在searcher中,用戶輸入字符串信息存儲在query中,兩個條件都具備了,可以開始查詢了。查詢的結(jié)果存放在
29、Hits的對象hits當中。需要說明的是,Search有很多個重載方法,但最終都要調(diào)用Hits的構(gòu)造函數(shù)實現(xiàn)查詢工作。本文以構(gòu)造函數(shù)Hits(Searcher s, Query q, Filter f)為例。其中s、q分別為上面?zhèn)魅氲膮?shù)searcher、query,f為過濾器,這里默認為null。Hits(Searcher s, Query q, Filter f)的功能實現(xiàn)主要通過兩個關(guān)鍵語句。51 weight = q.Weight(s);該方法主要計算權(quán)重,是查詢結(jié)果的排序工作的依據(jù)之一。Weight是一個接口類,它存在的目的是使檢索不改變一個Query,使得Query可以重用,即實現(xiàn)
30、Query對象的重復使用。511 Query query = searcher.Rewrite(this);實現(xiàn)query的重寫。重寫的目的是似的最終的query存放的是本次查詢用戶輸入的信息,即第4部分返回的對象q。它的實現(xiàn)方法很簡單,如果是第一次查詢,則直接返回;如果不是第一次查詢,則將本次查詢對象賦值給query然后再返回。512 Weight weight = query.CreateWeight(searcher);該方法中,有一個判斷語句if (terms.Count = 1),即如果只有一個token,那么由于不存在兩個token之間的位置關(guān)系,可以直接計算權(quán)重,如果不止一個to
31、ken,那么調(diào)用方法PhraseWeight(this, searcher);this就是本次query。5121 PhraseWeight(this, searcher)該方法首先進行一些初始化工作,相似度等參數(shù)都采用默認的值。接著調(diào)用idf = similarity.Idf(Enclosing_Instance.terms, searcher)計算每個短語的得分因子idf。5122 float sum = weight.SumOfSquaredWeights();計算得分。5123 float norm = GetSimilarity(searcher).QueryNorm(sum);計算
32、標準化因子。5124 weight.Normalize(norm);對權(quán)重進行標準化處理。 關(guān)于各種因子的計算方法,請參考附件一。52 GetMoreDocs(50);返回查詢結(jié)果得分最高的前100條。在lucene2.0當中,查詢結(jié)果如果足夠多,并不是一次性的全部返回,lucene默認先返回得分最高的100條,一般來講,這100條已經(jīng)能夠滿足用戶的需求了;如果還不能滿足用戶的需求 ,根據(jù)用戶需要,再返回得分次高的200條,接著400條依次類推,直到滿足用戶需求為止。該方法的實現(xiàn)如下:private void GetMoreDocs(int min)if (hitDocs.Count >
33、 min)min = hitDocs.Count;int n = min * 2; / double # retrievedTopDocs topDocs = (sort = null) ? searcher.Search(weight, filter, n) : searcher.Search(weight, filter, n, sort);length = topDocs.totalHits;ScoreDoc scoreDocs = topDocs.scoreDocs;float scoreNorm = 1.0f;if (length > 0 && topDocs.
34、GetMaxScore() > 1.0f)scoreNorm = 1.0f / topDocs.GetMaxScore();int end = scoreDocs.Length < length?scoreDocs.Length:length;for (int i = hitDocs.Count; i < end; i+)hitDocs.Add(new HitDoc(scoreDocsi.score * scoreNorm, scoreDocsi.doc);521 TopDocs topDocs = (sort = null) ? searcher.Search(weight
35、, filter, n) : searcher.Search(weight, filter, n, sort); 由于之前調(diào)用的Hits構(gòu)造函數(shù)沒有為sort賦值,因此這里sortnull。所以執(zhí)行searcher.Search(weight, filter, n);這里n為默認值100。5211 TopDocCollector collector = new TopDocCollector(nDocs);聲明一個大小為nDocs的TopDocCollector對象collector,用于存放查詢的結(jié)果。5212 Search(weight, filter, collector);這句是查詢的
36、核心語句,實現(xiàn)真正的查詢功能。這里的filter為默認的null。52121 Scorer scorer = weight.Scorer(reader);1)TermPositions tps = new TermPositionsEnclosing_Instance.terms.Count;聲明一個TermPostions類型的數(shù)組,用于存放每個term在索引文件中的位置等信息。2)一個for循環(huán)當中,執(zhí)行TermPositions p = reader.TermPositions(Term) Enclosing_Instance.termsi);查詢每個term的位置: a)TermPos
37、itions termPositions = TermPositions();該方法最終調(diào)用SegmentTermDocs方法.frq文件輸入流frqStream、刪除文檔信息deletedDocs和跳躍間隔因子skipInterval,然后獲取.prx文件輸入流proxStream。 b)termPositions.Seek(term); 查找term在索引文件中的位置。TermInfo ti = parent.tis.Get(term);由于此時termPositions記錄了相關(guān)索引文件的輸入流,在該方法中,核心是調(diào)用GetIndexOffset(Term term)方法,采用折半查找的方式,查詢每個term在.tis當中的位置。該句執(zhí)行完后,ti記錄了該term出現(xiàn)的頻率docFreq、在.frq文件中的指針frepPointer、在.prx文件中的指針proxPointer、跳躍因子skipOffset。Seek(ti);有了以上的位置信息,就可以尋找索引中的該term了。3)ExactPhraseScorer(this, tps, Enclosing_Instance.GetPositions(), similarity, reader.Norms(Encl
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024-2030年中國聚砜行業(yè)競爭態(tài)勢及發(fā)展可行性分析報告
- 2024-2030年中國綠色溶劑市場發(fā)展面臨的挑戰(zhàn)及投資可行性分析報告
- 2024-2030年中國示范農(nóng)場行業(yè)發(fā)展現(xiàn)狀及投資規(guī)模分析報告版
- 2024-2030年中國硅灰石行業(yè)未來發(fā)展預測及投資規(guī)模分析報告權(quán)威版
- 高校遠程教學微課程制作方案
- 2024養(yǎng)殖場對外投資與合作開發(fā)合同
- 2024年度航空物流服務合同
- 醫(yī)院給水系統(tǒng)PE管熱熔安裝方案
- 2024年企業(yè)運營資金借款合同文本
- 兒童早期的認知發(fā)展-皮亞杰前運算階段(三座山實驗)
- 國開一體化平臺01588《西方行政學說》章節(jié)自測(1-23)試題及答案
- 2024年極兔速遞有限公司招聘筆試參考題庫附帶答案詳解
- 2024年威士忌酒相關(guān)公司行業(yè)營銷方案
- 網(wǎng)絡(luò)游戲危害課件
- 2024供電營業(yè)規(guī)則學習課件
- 鐵路給水排水設(shè)計規(guī)范(TB 10010-2016)
- GINA2023-哮喘防治指南解讀-課件
- 2024年上海市第二十七屆初中物理競賽初賽試題及答案
- 寢室設(shè)計方案方法與措施
- 收費站冬季安全注意事項
評論
0/150
提交評論