lucene 全文檢索簡介
一,信息檢索的過程簡介
全文檢索和數(shù)據(jù)庫應(yīng)用最大的不同在于:讓最相關(guān)的頭100條結(jié)果滿足98%以上用戶的需求
1,構(gòu)建文本庫
在開發(fā)功能前,一個信息檢索系統(tǒng)需要做些準(zhǔn)備工作,首先,必須要構(gòu)建一個文本數(shù)據(jù)庫,這個文本數(shù)據(jù)庫用來保存所有用戶可能檢索的信息。在這些信息的基礎(chǔ)上,確定索引中
的文本類型,文本類型是被系統(tǒng)所認(rèn)可的一種信息格式,這種格式應(yīng)當(dāng)具有可識別,冗余程度低的特點。一旦文本模型確定下來后,就不應(yīng)當(dāng)對其進行大的行動。
2,建立索引
有了這種文本模型后,就應(yīng)該根據(jù)數(shù)據(jù)庫內(nèi)的文本建立索引。索引可以大大的提高信息檢索的速度。目前,有許多索引的建立方式。采用哪種方式取決于信息檢索系統(tǒng)的規(guī)模。大型信息檢索系統(tǒng)(百度,google這樣的搜索)均采用倒排的方式來建立索引。
3,進行搜索
在文檔建立索引之后,就可以開始對其進行搜索。這時,通常都是由用戶提交一個檢索請求,請求將被分析,然后利用文本操作進行處理。對于真實的信息檢索系統(tǒng),在真正處理請求前,還可以對請求進行一些預(yù)處理,然后再將請求送到后臺,并返回用戶需要的信息。
4,對結(jié)果進行過濾
通常,在信息檢索系統(tǒng)檢索到用戶需要的信息后,還要做一步操作,就是將信息以一定的規(guī)則進行排序或過濾,再返回給用戶。這一步實際上關(guān)乎到最終用戶的體驗。
二,Lucene 索引
1,使用索引提高檢索速度常用的3種索引方式為
(1)倒排
倒排是一種面向單詞的索引機制。通常它由(關(guān)鍵字)和出現(xiàn)情況兩部分組成。對于索引中的每個詞(關(guān)鍵字),都跟隨一個列表(位置表),用來記錄單詞在所有文檔中出現(xiàn)的位置。
倒排的特點:
在倒排索引中,關(guān)鍵字的數(shù)量并非隨著文本內(nèi)容的增長也線性增長。這是因為無論多大數(shù)量的文本數(shù)據(jù)庫,總能夠規(guī)范出一個關(guān)鍵字表。這種關(guān)鍵字受到實際語言因素的限制,他的增長率在文本數(shù)據(jù)庫達(dá)到一定規(guī)模后可以忽略不計。
(2)后綴數(shù)組
(3)簽名文件
2,索引的Segment
每個segment代表lucene的一個完整索引段,通常,一個索引中,包含有多個segment,每個segment都有一個統(tǒng)一的前綴,這個前綴是根據(jù)當(dāng)前索引的document的數(shù)量而確立的,前綴名是document 數(shù)量轉(zhuǎn)成36進制后,在前面加上“_”而構(gòu)成的。
segment的格式
(1).fnm格式
包含了document中的所有field的名稱。
(2).fdx 和.fdt格式
.fdx 和.fdt是綜合使用的兩個文件,其中.fdt類型文件用于存儲具有store.YES屬性的field的數(shù)據(jù)。而.fdx類型文件則是一個索引,用于存儲document在.fdt中的位置。
(3).tii 與.tis格式
.tis 文件用于存儲分詞的詞條(Term),而.tii就是它的索引文件,它標(biāo)明了每個.tis文件的詞條的位置。
(4).cfs復(fù)合格式
在indexWriter 中有一個屬性:useCompoundFile,它的默認(rèn)值為True,這個屬性的含義是:是否使用復(fù)合索引格式來保存索引。索引的內(nèi)容可能非常大,文件的數(shù)量可能非常多,如果遇到這種情況,系統(tǒng)打開文件數(shù)量巨大將會極大地耗費系統(tǒng)資源。因此, lucene提供了一種簡單文件索引格式,也就是所謂的復(fù)合索引格式。
3,索引的優(yōu)化
(1)合并因子mergeFactor
當(dāng)mergeFactor取比較小的值時,內(nèi)存中注入的文檔數(shù)量少,向磁盤寫入segment的操作比較多,故此時將占用較少的內(nèi)存,但是索引的建立由于i/o操作頻繁所以會比較慢.而當(dāng)mergeFactor取較大的值時,內(nèi)存中駐留的document數(shù)量比較多.向磁盤寫入segment的操作較少,故此時將占用較多的內(nèi)存,但索引的建立速度比較快.
maxMergeDocs
對索引的合并的最多文檔數(shù)量.
mixMergeDocs(maxBufferedDocs)
(2)索引的合并與索引的優(yōu)化
FSDirectory 和 RAMDirectory目錄文件
FSDirectory 是與文件系統(tǒng)目錄有關(guān)的,而RAMDirectory則是與內(nèi)存相關(guān)的。
對于lucene 來說,兩中目錄都可以作為索引的存儲路徑。在初始化indexwriter的時候需要傳入一個directory類型的對象作為參數(shù)之一,當(dāng)indexwriter接收這樣的參數(shù)時(無論是fsdirectory還是ramdirectory),它都會在指定的位置下將索引進行存儲。但是文件系統(tǒng)目錄就會直接將索引寫到磁盤上。而ramdirectory則是在內(nèi)存中一個區(qū)域,雖然向其中添加document的過程與使用fsdirectory一樣,但是由于它是內(nèi)存中的一塊區(qū)域,因此如果不將ramdirectory中的內(nèi)存寫入磁盤,當(dāng)虛擬機退出后,里面的內(nèi)容也會隨之消失。因此,需要將ramdirectory中的內(nèi)容轉(zhuǎn)到fsdirectory中。
(3)使用indexWriter來合并索引
document可以被放置在 ramdirectory中,使用它的優(yōu)點就是索引的速度很快。當(dāng)document被加入到ramdirectory中后, ramdirectory在邏輯上就是一個完整的索引了,它在邏輯上就應(yīng)當(dāng)包括如前所說的所有索引格式的文件(但是不能被持久的保存起來)。
indexwriter的addindexs()方法,可以實現(xiàn)索引的合并。addindexs()方法的參數(shù)是一個directory類型的數(shù)組,因此,可以同時合并多個目錄下的索引,只要分別為這些目錄創(chuàng)建其對應(yīng)的directory類型的對象就可以了。
(4)索引的優(yōu)化
indexwriter的optimize()方法正是為了這個目的而設(shè)置的,該方法能夠?qū)Ξ?dāng)前indexwriter所制定的索引目錄以及其所使用的緩存目錄下的所有segment 進行優(yōu)化,使所有的segments合并成一個完整的segement,即整個索引目錄內(nèi)出現(xiàn)一種文件前綴。
對于系統(tǒng)的優(yōu)化會有什么性能上的損失呢?由于優(yōu)化時需要對已有的索引內(nèi)的文件進行操作,因此需要耗費更多的內(nèi)存和磁盤空間,索引優(yōu)化采用的策略是建立新的segment來取代那些被合并的segements,所以在舊的segement還未被刪除之前,索引內(nèi)的磁盤空間消耗將會非常大,甚至可能使原來索引的兩倍。同理,在進行優(yōu)化時的磁盤i/o也會非常多,所以這是一個耗費資源的過程。
2,索引中刪除文檔
索引的讀取工具IndexReader,IndexReader中的getVersion方法可以查看當(dāng)前索引的版本,這個version是索引建立時的精確到毫秒的時間,IndexReader的indexReader.numDosc()方法,可以查看當(dāng)前索引內(nèi)總共有多少個document,IndexReader.document(int)方法可以從索引中取出相應(yīng)的document.
(1)使用文檔的id號來刪除特定文檔
在創(chuàng)建索引的過程中 lucene會為每一個加入索引的document賦予一個id號,這個id號將唯一的標(biāo)識每個文檔.reader.deleteDocument(int),int參數(shù)為id 號刪除完畢后需要執(zhí)行reader.close()方法關(guān)閉.使刪除操作寫入索引的deletable文件中,如果不關(guān)閉怎沒有刪除掉,實際上lucene的刪除機制為回收站機制,刪除操作沒有真正刪除文件,而是做了一標(biāo)記,可以進行還原;reader.undeleteall()方法可以幫助實現(xiàn)反刪除(當(dāng)使用indexwriter對索引optimize一次時,lucene 為每個document重新分配id,這樣那些被標(biāo)記為已刪除的document真正的被物理刪除了).
(2)使用field信息來刪除批量文檔
reader.deleteDocuments(term)該方法是一個能夠批量刪除索引的方法,它刪除索引是按照詞條來進行的. term類是用于表示詞條的一個工具,它能夠?qū)⒃~條表示成<field,value>(例如: 詞條為<bookename,男>也就是indexReader就會刪除所有在"bookname"這個field中含有"男"這個term的document).
3, lucene的同步問題
writer.lock
出現(xiàn)在向索引中添加文檔時,或是將文檔從索引中刪除時,在indexwriter的close()方法被調(diào)用時被釋放
commit.lock
主要是與segment合并和讀取的操作相關(guān).
indexModifier類
三,lucene 的搜索
1,indexSearcher進行搜索
(1)indexSearcher的簡單使用:
indexSearcher searcher=new IndexSearcher("索引路徑");
//構(gòu)建一個term對象
Term term=new Term("name","女")
//構(gòu)建一個query對象
Query q =new TermQuery (term);
//檢索
Hits hits=searcher.search(q);
//顯示結(jié)果
for(int i=0;i<hits.lengtth();i++){
system.out.println(hits.doc(i));
}
上面的例子中介紹了indexsearcher的search方法,search方法是整個檢索系統(tǒng)的核心。
indexSearcher有多種重載search方法,這些方法有些在于indexSearcher的父類Search中,有些在本身,Search類實現(xiàn)了一個接口Searchable,該接口提供了可以搜索的功能。
(2)Hits對象是搜索結(jié)果的集合 主要有下面幾個方法 [list=1]
length() ,這個方法記錄有多少條結(jié)果返回(lazy loading)
doc(n) 返回第n個記錄
id(in) 返回第n個記錄的Document ID
score(n) 第n個記錄的相關(guān)度(積分)
由于搜索的結(jié)果一般比較大,從性能上考慮,Hits對象并不會真正把所有的結(jié)果全部取回,默認(rèn)情況下是保留前100個記錄(對于一般的搜索引擎,100個記錄足夠了).
hits類,在上面的例子中使用了hits,從Hits的doc(int n)方法來研究Hits的工作原理.
doc(int n)方法用于搜索索引的返回結(jié)果中取出相應(yīng)的文檔。參數(shù)n代表結(jié)果中的第n個文檔。而doc(int n)方法的第一步就是使用hitDoc(int n)方法從緩存中取去相應(yīng)的文檔。
在hitDoc(int n)方法中,會先判斷當(dāng)前用戶需要取出的文檔是不是已經(jīng)超過了緩存的大小。如果是,則先調(diào)用getMoreDocs(int min)方法來擴大緩存,然后再從緩存中返回需要的文檔。
2,搜索結(jié)構(gòu)的評分
(1)文檔的得分算法公式:略
搜索的結(jié)果可以按照分?jǐn)?shù)來排序。
3,lucene內(nèi)建的query對象
(1) 內(nèi)建的query對象主要包括:
(2)TermQuery詞條搜索
(3) BooleanQuery布爾搜索
(4)RangeQuery范圍搜索
(5) PrefixQuery前綴搜索
(6) PhraseQuery短語搜索
(7)MultiPhraseQuery多短語搜索
(8)FuzzyQuery模糊搜索
(9)WildcardQuery通配符搜索
(10)SpanQuery跨度搜索
(11)還有第三方提供的Query對象:RegexQuery
上面的這些內(nèi)建的query對象都是可以用來做根據(jù)不同的情況來進行搜索。(具體略)
4,Lucene查詢總結(jié):
Lucene面向全文檢索的優(yōu)化在于首次索引檢索后,并不把所有的記錄(Document)具體內(nèi)容讀取出來,而起只將所有結(jié)果中匹配度最高的頭100條結(jié)果(TopDocs)的ID放到結(jié)果集緩存中并返回,這里可以比較一下數(shù)據(jù)庫檢索:如果是一個10,000條的數(shù)據(jù)庫檢索結(jié)果集,數(shù)據(jù)庫是一定要把所有記錄內(nèi)容都取得以后再開始返回給應(yīng)用結(jié)果集的。所以即使檢索匹配總數(shù)很多,Lucene的結(jié)果集占用的內(nèi)存空間也不會很多。對于一般的模糊檢索應(yīng)用是用不到這么多的結(jié)果的,頭100條已經(jīng)可以滿足90%以上的檢索需求。
如果首批緩存結(jié)果數(shù)用完后還要讀取更后面的結(jié)果時Searcher會再次檢索并生成一個上次的搜索緩存數(shù)大1倍的緩存,并再重新向后抓取。所以如果構(gòu)造一個Searcher去查1-120條結(jié)果,Searcher其實是進行了2次搜索過程:頭100條取完后,緩存結(jié)果用完,Searcher重新檢索再構(gòu)造一個200條的結(jié)果緩存,依此類推,400條緩存,800條緩存。由于每次Searcher對象消失后,這些緩存也訪問那不到了,你有可能想將結(jié)果記錄緩存下來,緩存數(shù)盡量保證在100以下以充分利用首次的結(jié)果緩存,不讓Lucene浪費多次檢索,而且可以分級進行結(jié)果緩存。
Lucene的另外一個特點是在收集結(jié)果的過程中將匹配度低的結(jié)果自動過濾掉了。這也是和數(shù)據(jù)庫應(yīng)用需要將搜索的結(jié)果全部返回不同之處.
四,排序、過濾、分頁
1, 自然排序
相關(guān)度排序是一種最簡單的排序方式,所謂相關(guān)度,其實就是文檔的得分。
searcher的explain方法可以每一個文檔的得分是怎么樣的算出來的,他們的idf,tf,lengthNorm的值得情況。如:searcher.explain(q,hits.id(i).toString());
通過改變boost值來改變文檔的得分在進行相關(guān)度排序的時候,如果想人為的增加某個文檔的相關(guān)度,使其在搜索的結(jié)構(gòu)中排在考前的位置上,可以使用boost。
如:索引寫入document 的時,在寫入之前,使用document方法(document.setBoost(3f))
原理:在lucene中,文檔的boost的值一般情況默認(rèn)為1.0,但當(dāng)某個文檔的boost值大于1.0后,所有的文檔boost值均會除以這個最大值,以此來為每個文檔獲取一個小于1.0的數(shù)作為新的boost值。
2,使用Sort來排序
Sort是lucene自帶的一個排序工具,通過它,可以方便地對檢索結(jié)果進行排序。
Sort所提供的排序功能是以field為基礎(chǔ)的,也就是說,最終的排序準(zhǔn)則,總是以某個field(或多個)的值為基礎(chǔ),經(jīng)過這樣的處理,最終的排序就轉(zhuǎn)變成對所有文檔中同一個field(或多個field)的值的排序。方法:Sort(String field,boolean reverse),field表示參照制定的field排序,第二個參數(shù)reverse 表示排序的順序,升序還是降序(reverse的默認(rèn)值為false,升序排序)。
SortField是一個包裝類型,通過它的包裝,可以使Sort類清楚地了解要進行排序的field的各種信息。
構(gòu)造函數(shù)(略)
按文檔的內(nèi)部id號來排序
如:Hits hits=searcher.search(q,Sort.INDEXORDER);
這個內(nèi)部需要是在建立索引的時候自動創(chuàng)建的。
按一個或多個Field來排序
如:Sort sort=new Sort();//定義一個Sort對象
SortField f=new SortField("bookno",SortField.INT,false);//定義SortField對象,同時是按照bookno升序來排序的。
sort.setSort(f);
//下面就可以查找排序了.
3,搜索的過濾器
lucene 中有兩種過濾器,一個是搜索時的過濾器,一個是分析的過濾.
搜索時的過濾是一種減小搜索范圍的方式.同時也可以實現(xiàn)一種安全機制,即保護某些文檔無法被檢索.
搜索時的過濾器來自于一個抽象基類Filter,它定義了過濾器的基本行為
public abstract BitSet bits(IndexReader reader);可以看到,這個方法返回一個bitSet類型的對象,filter是一種過濾行為,這種過濾行為在搜索時的表現(xiàn)就是"視而不見" ,即遇到該文檔時,發(fā)現(xiàn)它被"過濾"了,于是就忽略它,BitSet是一種"位集合"隊列,這個隊列中的每個元素都只有兩種取值,即true或false,這倆種值代表文檔是否被過濾,也就是說,返回結(jié)果時,會首先遍歷BitSet盡將那些對應(yīng)值為true的文檔返回。在BitSet集合中,將其索引號看作是文檔的內(nèi)部id。
lucene中內(nèi)置了幾個Filter,
RangeFilter(范圍過濾,詳細(xì)略)
QueryFilter(重要)在結(jié)果中查詢
實際應(yīng)用:在filter的行為可以看到,它總是在搜索前,首先對索引進行一次遍歷,然后返回一個被業(yè)務(wù)邏輯處理好的BitSet對象,這種做法無可厚非,但是卻存在很嚴(yán)重的性能
問題,這相當(dāng)于對索引進行了兩次遍歷,這樣會降低性能。
CachingWrapppeFilter將一個Filter作為構(gòu)造函數(shù)的參數(shù)傳入,在需要使用原Filter的地方,將這個CachingWrapppeFilter的對象傳入,就可以在原來的filter進行過濾了。
CachingWrapppeFilter的原理: 其中使用了緩存,在調(diào)用的時候,查看緩存中是否存在處理的結(jié)構(gòu),如果存在,則直接取出后返回,如果沒有執(zhí)行被注入的Filter.
4,Lucene翻頁
(1)依賴于session的翻頁
是指將搜索的結(jié)果存儲于session中,用戶翻頁的時候就從session中取出hits集合,這種方式簡單不需要什么算法,一次查詢就可以獲得結(jié)果,但是這樣很容易造成服務(wù)器的內(nèi)存溢出。
(2)多次查詢
使用完全無狀態(tài)保持的開發(fā)方式,即用戶每次翻頁,都對索引進行重新檢索,然后取得當(dāng)前頁的結(jié)果并返回。
(3)緩存+多次查詢
使用session方式的查詢有內(nèi)存問題,但是如果采用完全無狀態(tài)的查詢方式,又會出現(xiàn)磁盤i/o太過頻繁的問題,以致降低了效率。可以采用在session或者內(nèi)存中其他空間,另外在緩存一部分結(jié)果,比如后5頁10頁等,這樣當(dāng)進行翻頁的時候,就可以從緩存中取出內(nèi)容,不用重新查索引,如果沒有緩存,則重新查詢,更新緩存。
(4)緩存+多次查詢+數(shù)據(jù)庫
這種方式是在上面的基礎(chǔ)上增加的,如果索引的量很大可以考慮把內(nèi)容多的東西放在數(shù)據(jù)庫中,索引中的id和數(shù)據(jù)庫庫中的id同步。
搜索時的過濾器可以自己定義.
5,lucene的分析器
信息檢索所要處理的主要對象就是信息,在實際應(yīng)用中,大部分時候信息是以一種文本的方式呈現(xiàn)的。而信息檢索的第一件事,就是要對這種文本進行分析,以便能夠繼續(xù)下面的處理。
(1) 分詞
分詞就是將一段文本拆分成多個詞。(需要注意的一點是,在建立索引時使用的分詞工具,與在分析用戶的檢索請求時使用的分詞工具應(yīng)當(dāng)是同一個)。
(2)分詞器的結(jié)構(gòu)
一個標(biāo)準(zhǔn)的分詞器是由兩部分組成,一部分是分詞器,被稱為Tokenizer;另一部分是過濾器,被稱為TokenFilter。一個分析器往往是由一個分詞器和多個過濾器組成。這里所說的過濾器,與前面所說的檢索時使用的過濾器完全是不同的兩個概念。此處的Filter主要是用于對用戶切出來的詞進行一些處理,如去掉一些敏感詞、轉(zhuǎn)換大小寫、轉(zhuǎn)換單復(fù)數(shù)等。
lucene內(nèi)部提供了過濾器,StandarFilter,StopFilter,LowerCaseFilter
(待續(xù).........................................)