1. 全文檢索系統(tǒng)與Lucene簡(jiǎn)介
1.1 什么是全文檢索與全文檢索系統(tǒng)
全文檢索是指計(jì)算機(jī)索引程序通過掃描文章中的每一個(gè)詞,對(duì)每一個(gè)詞建立一個(gè)索引,指明該詞在文章中出現(xiàn)的次數(shù)和位置,當(dāng)用戶查詢時(shí),檢索程序就根據(jù)事先建立的索引進(jìn)行查找,并將查找的結(jié)果反饋給用戶的檢索方式。這個(gè)過程類似于通過字典中的檢索字表查字的過程。
全文檢索的方法主要分為按字檢索和按詞檢索兩種。按字檢索是指對(duì)于文章中的每一個(gè)字都建立索引,檢索時(shí)將詞分解為字的組合。對(duì)于各種不同的語言而言,字有不同的含義,比如英文中字與詞實(shí)際上是合一的,而中文中字與詞有很大分別。按詞檢索指對(duì)文章中的詞,即語義單位建立索引,檢索時(shí)按詞檢索,并且可以處理同義項(xiàng)等。英文等西方文字由于按照空白切分詞,因此實(shí)現(xiàn)上與按字處理類似,添加同義處理也很容易。中文等東方文字則需要切分字詞,以達(dá)到按詞索引的目的,關(guān)于這方面的問題,是當(dāng)前全文檢索技術(shù)尤其是中文全文檢索技術(shù)中的難點(diǎn),在此不做詳述。
全文檢索系統(tǒng)是按照全文檢索理論建立起來的用于提供全文檢索服務(wù)的軟件系統(tǒng)。一般來說,全文檢索需要具備建立索引和提供查詢的基本功能,此外現(xiàn)代的全文檢索系統(tǒng)還需要具有方便的用戶接口、面向WWW[1]的開發(fā)接口、二次應(yīng)用開發(fā)接口等等。功能上,全文檢索系統(tǒng)核心具有建立索引、處理查詢返回結(jié)果集、增加索引、優(yōu)化索引結(jié)構(gòu)等等功能,外圍則由各種不同應(yīng)用具有的功能組成。結(jié)構(gòu)上,全文檢索系統(tǒng)核心具有索引引擎、查詢引擎、文本分析引擎、對(duì)外接口等等,加上各種外圍應(yīng)用系統(tǒng)等等共同構(gòu)成了全文檢索系統(tǒng)。圖1.1展示了上述全文檢索系統(tǒng)的結(jié)構(gòu)與功能。
在上圖中,我們看到:全文檢索系統(tǒng)中最為關(guān)鍵的部分是全文檢索引擎,各種應(yīng)用程序都需要建立在這個(gè)引擎之上。一個(gè)全文檢索應(yīng)用的優(yōu)異程度,根本上由全文檢索引擎來決定。因此提升全文檢索引擎的效率即是我們提升全文檢索應(yīng)用的根本。另一個(gè)方面,一個(gè)優(yōu)異的全文檢索引擎,在做到效率優(yōu)化的同時(shí),還需要具有開放的體系結(jié)構(gòu),以方便程序員對(duì)整個(gè)系統(tǒng)進(jìn)行優(yōu)化改造,或者是添加原有系統(tǒng)沒有的功能。比如在當(dāng)今多語言處理的環(huán)境下,有時(shí)需要給全文檢索系統(tǒng)添加處理某種語言或者文本格式的功能,比如在英文系統(tǒng)中添加中文處理功能,在純文本系統(tǒng)中添加XML或者HTML格式的文本處理功能,系統(tǒng)的開放性和擴(kuò)充性就十分的重要。
1.2 什么是Lucene
Lucene是apache軟件基金會(huì)jakarta項(xiàng)目組的一個(gè)子項(xiàng)目,是一個(gè)開放源代碼的全文檢索引擎工具包,即它不是一個(gè)完整的全文檢索引擎,而是一個(gè)全文檢索引擎的架構(gòu),提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene的目的是為軟件開發(fā)人員提供一個(gè)簡(jiǎn)單易用的工具包,以方便的在目標(biāo)系統(tǒng)中實(shí)現(xiàn)全文檢索的功能,或者是以此為基礎(chǔ)建立起完整的全文檢索引擎。
Lucene的原作者是Doug Cutting,他是一位資深全文索引/檢索專家,曾經(jīng)是V-Twin搜索引擎的主要開發(fā)者,后在Excite擔(dān)任高級(jí)系統(tǒng)架構(gòu)設(shè)計(jì)師,目前從事于一些Internet底層架構(gòu)的研究。早先發(fā)布在作者自己的http://www.lucene.com/,后來發(fā)布在SourceForge,2001年年底成為apache軟件基金會(huì)jakarta的一個(gè)子項(xiàng)目:http://jakarta.apache.org/lucene/。
1.3 Lucene的應(yīng)用、特點(diǎn)及優(yōu)勢(shì)
作為一個(gè)開放源代碼項(xiàng)目,Lucene從問世之后,引發(fā)了開放源代碼社群的巨大反響,程序員們不僅使用它構(gòu)建具體的全文檢索應(yīng)用,而且將之集成到各種系統(tǒng)軟件中去,以及構(gòu)建Web應(yīng)用,甚至某些商業(yè)軟件也采用了Lucene作為其內(nèi)部全文檢索子系統(tǒng)的核心。apache軟件基金會(huì)的網(wǎng)站使用了Lucene作為全文檢索的引擎,IBM的開源軟件eclipse的2.1版本中也采用了Lucene作為幫助子系統(tǒng)的全文索引引擎,相應(yīng)的IBM的商業(yè)軟件Web Sphere中也采用了Lucene。Lucene以其開放源代碼的特性、優(yōu)異的索引結(jié)構(gòu)、良好的系統(tǒng)架構(gòu)獲得了越來越多的應(yīng)用。
Lucene作為一個(gè)全文檢索引擎,其具有如下突出的優(yōu)點(diǎn):
(1)索引文件格式獨(dú)立于應(yīng)用平臺(tái)。Lucene定義了一套以8位字節(jié)為基礎(chǔ)的索引文件格式,使得兼容系統(tǒng)或者不同平臺(tái)的應(yīng)用能夠共享建立的索引文件。
(2)在傳統(tǒng)全文檢索引擎的倒排索引的基礎(chǔ)上,實(shí)現(xiàn)了分塊索引,能夠針對(duì)新的文件建立小文件索引,提升索引速度。然后通過與原有索引的合并,達(dá)到優(yōu)化的目的。
(3)優(yōu)秀的面向?qū)ο蟮南到y(tǒng)架構(gòu),使得對(duì)于Lucene擴(kuò)展的學(xué)習(xí)難度降低,方便擴(kuò)充新功能。
(4)設(shè)計(jì)了獨(dú)立于語言和文件格式的文本分析接口,索引器通過接受Token流完成索引文件的創(chuàng)立,用戶擴(kuò)展新的語言和文件格式,只需要實(shí)現(xiàn)文本分析的接口。
(5)已經(jīng)默認(rèn)實(shí)現(xiàn)了一套強(qiáng)大的查詢引擎,用戶無需自己編寫代碼即使系統(tǒng)可獲得強(qiáng)大的查詢能力,Lucene的查詢實(shí)現(xiàn)中默認(rèn)實(shí)現(xiàn)了布爾操作、模糊查詢(Fuzzy Search)、分組查詢等等。
面對(duì)已經(jīng)存在的商業(yè)全文檢索引擎,Lucene也具有相當(dāng)?shù)膬?yōu)勢(shì):
首先,它的開發(fā)源代碼發(fā)行方式(遵守Apache Software License),在此基礎(chǔ)上程序員不僅僅可以充分的利用Lucene所提供的強(qiáng)大功能,而且可以深入細(xì)致的學(xué)習(xí)到全文檢索引擎制作技術(shù)和面相對(duì)象編程的實(shí)踐,進(jìn)而在此基礎(chǔ)上根據(jù)應(yīng)用的實(shí)際情況編寫出更好的更適合當(dāng)前應(yīng)用的全文檢索引擎。在這一點(diǎn)上,商業(yè)軟件的靈活性遠(yuǎn)遠(yuǎn)不及Lucene。其次,Lucene秉承了開放源代碼一貫的架構(gòu)優(yōu)良的優(yōu)勢(shì),設(shè)計(jì)了一個(gè)合理而極具擴(kuò)充能力的面向?qū)ο蠹軜?gòu),程序員可以在Lucene的基礎(chǔ)上擴(kuò)充各種功能,比如擴(kuò)充中文處理能力,從文本擴(kuò)充到HTML、PDF等等文本格式的處理,編寫這些擴(kuò)展的功能不僅僅不復(fù)雜,而且由于Lucene恰當(dāng)合理的對(duì)系統(tǒng)設(shè)備做了程序上的抽象,擴(kuò)展的功能也能輕易的達(dá)到跨平臺(tái)的能力。最后,轉(zhuǎn)移到apache軟件基金會(huì)后,借助于apache軟件基金會(huì)的網(wǎng)絡(luò)平臺(tái),程序員可以方便的和開發(fā)者、其它程序員交流,促成資源的共享,甚至直接獲得已經(jīng)編寫完備的擴(kuò)充功能。最后,雖然Lucene使用Java語言寫成,但是開放源代碼社區(qū)的程序員正在不懈的將之使用各種傳統(tǒng)語言實(shí)現(xiàn)(例如.net framework),在遵守Lucene索引文件格式的基礎(chǔ)上,使得Lucene能夠運(yùn)行在各種各樣的平臺(tái)上,系統(tǒng)管理員可以根據(jù)當(dāng)前的平臺(tái)適合的語言來合理的選。
2. Lucene系統(tǒng)結(jié)構(gòu)分析
2.1 系統(tǒng)結(jié)構(gòu)組織
Lucene作為一個(gè)優(yōu)秀的全文檢索引擎,其系統(tǒng)結(jié)構(gòu)具有強(qiáng)烈的面向?qū)ο筇卣鳌J紫仁嵌x了一個(gè)與平臺(tái)無關(guān)的索引文件格式,其次通過抽象將系統(tǒng)的核心組成部分設(shè)計(jì)為抽象類,具體的平臺(tái)實(shí)現(xiàn)部分設(shè)計(jì)為抽象類的實(shí)現(xiàn),此外與具體平臺(tái)相關(guān)的部分比如文件存儲(chǔ)也封裝為類,經(jīng)過層層的面向?qū)ο笫降奶幚恚罱K達(dá)成了一個(gè)低耦合高效率,容易二次開發(fā)的檢索引擎系統(tǒng)。
以下將討論Lucene系統(tǒng)的結(jié)構(gòu)組織,并給出系統(tǒng)結(jié)構(gòu)與源碼組織圖:
從圖中我們清楚的看到,Lucene的系統(tǒng)由基礎(chǔ)結(jié)構(gòu)封裝、索引核心、對(duì)外接口三大部分組成。其中直接操作索引文件的索引核心又是系統(tǒng)的重點(diǎn)。Lucene的將所有源碼分為了7個(gè)模塊(在java語言中以包即package來表示),各個(gè)模塊所屬的系統(tǒng)部分也如上圖所示。需要說明的是org.apache.lucene.queryPaser是做為org.apache.lucene.search的語法解析器存在,不被系統(tǒng)之外實(shí)際調(diào)用,因此這里沒有當(dāng)作對(duì)外接口看待,而是將之獨(dú)立出來。
從面象對(duì)象的觀點(diǎn)來考察,Lucene應(yīng)用了最基本的一條程序設(shè)計(jì)準(zhǔn)則:引入額外的抽象層以降低耦合性。首先,引入對(duì)索引文件的操作org.apache.lucene.store的封裝,然后將索引部分的實(shí)現(xiàn)建立在(org.apache.lucene.index)其之上,完成對(duì)索引核心的抽象。在索引核心的基礎(chǔ)上開始設(shè)計(jì)對(duì)外的接口org.apache.lucene.search與org.apache.lucene.analysis。在每一個(gè)局部細(xì)節(jié)上,比如某些常用的數(shù)據(jù)結(jié)構(gòu)與算法上,Lucene也充分的應(yīng)用了這一條準(zhǔn)則。在高度的面向?qū)ο罄碚摰闹蜗拢沟肔ucene的實(shí)現(xiàn)容易理解,易于擴(kuò)展。
Lucene在系統(tǒng)結(jié)構(gòu)上的另一個(gè)特點(diǎn)表現(xiàn)為其引入了傳統(tǒng)的客戶端服務(wù)器結(jié)構(gòu)以外的的應(yīng)用結(jié)構(gòu)。Lucene可以作為一個(gè)運(yùn)行庫被包含進(jìn)入應(yīng)用本身中去,而不是做為一個(gè)單獨(dú)的索引服務(wù)器存在。這自然和Lucene開放源代碼的特征分不開,但是也體現(xiàn)了Lucene在編寫上的本來意圖:提供一個(gè)全文索引引擎的架構(gòu),而不是實(shí)現(xiàn)。
2.2 數(shù)據(jù)流分析
了解數(shù)據(jù)流分析的重要性:
理解Lucene系統(tǒng)結(jié)構(gòu)的另一個(gè)方式是去探討其中數(shù)據(jù)流的走向,并以此摸清楚Lucene系統(tǒng)內(nèi)部的調(diào)用時(shí)序。在此基礎(chǔ)上,我們能夠更加深入的理解Lucene的系統(tǒng)結(jié)構(gòu)組織,以方便以后在Lucene系統(tǒng)上的開發(fā)工作。這部分的分析,是深入Lucene系統(tǒng)的鑰匙,也是進(jìn)行重寫的基礎(chǔ)。
Lucene系統(tǒng)中的主要的數(shù)據(jù)流以及它們之間的關(guān)系圖:
圖2.2很好的表明了Lucene在內(nèi)部的數(shù)據(jù)流組織情況,并且沿著數(shù)據(jù)流的方向我們也可以對(duì)與Lucene內(nèi)部的執(zhí)行時(shí)序有一個(gè)清楚的了解。現(xiàn)在將圖中的涉及到的流的類型與各個(gè)邏輯對(duì)應(yīng)系統(tǒng)的相關(guān)部分的關(guān)系說明一下。
圖中共存在4種數(shù)據(jù)流,分別是文本流、token流、字節(jié)流與查詢語句對(duì)象流。文本流表示了對(duì)于索引目標(biāo)和交互控制的抽象,即用文本流表示了將要索引的文件,用文本流向用戶輸出信息;在實(shí)際的實(shí)現(xiàn)中,Lucene中的文本流采用了UCS-2作為編碼,以達(dá)到適應(yīng)多種語言文字的處理的目的。Token流是Lucene內(nèi)部所使用的概念,是對(duì)傳統(tǒng)文字中的詞的概念的抽象,也是Lucene在建立索引時(shí)直接處理的最小單位;簡(jiǎn)單的講Token就是一個(gè)詞和所在域值的組合,后面在敘述文件格式時(shí)也將繼續(xù)涉及到token,這里不詳細(xì)展開。字節(jié)流則是對(duì)文件抽象的直接操作的體現(xiàn),通過固定長(zhǎng)度的字節(jié)(Lucene定義為8比特位長(zhǎng),后面文件格式將詳細(xì)敘述)流的處理,將文件操作解脫出來,也做到了與平臺(tái)文件系統(tǒng)的無關(guān)性。查詢語句對(duì)象流則是僅僅在查詢語句解析時(shí)用到的概念,它對(duì)查詢語句抽象,通過類的繼承結(jié)構(gòu)反映查詢語句的結(jié)構(gòu),將之傳送到查找邏輯來進(jìn)行查找的操作。
圖中的涉及到了多種邏輯,基本上直接對(duì)應(yīng)于系統(tǒng)某一模塊,但是也有跨模塊調(diào)用的問題發(fā)生,這是因?yàn)長(zhǎng)ucene的重用程度非常好,因此很多實(shí)現(xiàn)直接調(diào)用了以前的工作成果,這在某種程度上其實(shí)是加強(qiáng)了模塊耦合性,但是也是為了避免系統(tǒng)的過于龐大和不必要的重復(fù)設(shè)計(jì)的一種折衷體現(xiàn)。詞法分析邏輯對(duì)應(yīng)于org.apache.lucene.analysis部分。查詢語句語法分析邏輯對(duì)應(yīng)于org.apache.lucene.queryParser部分,并且調(diào)用了org.apache.lucene.analysis的代碼。查詢結(jié)束之后向評(píng)分排序邏輯輸出token流,繼而由評(píng)分排序邏輯處理之后給出文本流的結(jié)果,這一部分的實(shí)現(xiàn)也包含在了org.apache.lucene.search中。索引構(gòu)建邏輯對(duì)應(yīng)于org.apache.lucene.index部分。索引查找邏輯則主要是org.apache.lucene.search,但是也大量的使用了org.apache.lucene.index部分的代碼和接口定義。存儲(chǔ)抽象對(duì)應(yīng)于org.apache.lucene.store。沒有提到的模塊則是做為系統(tǒng)公共基礎(chǔ)設(shè)施存在。
2.3 基于Lucene的應(yīng)用開發(fā)
首先,我們需要的是按照目標(biāo)語言的詞法結(jié)構(gòu)來構(gòu)建相應(yīng)的詞法分析邏輯,實(shí)現(xiàn)Lucene在org.apache.lucene.analysis中定義的接口,為L(zhǎng)ucene提供目標(biāo)系統(tǒng)所使用的語言處理能力。Lucene默認(rèn)的已經(jīng)實(shí)現(xiàn)了英文和德文的簡(jiǎn)單詞法分析邏輯(按照空格分詞,并去除常用的語法詞,如英語中的is,am,are等等)。在這里,主要需要參考實(shí)現(xiàn)的接口在org.apache.lucene.analysis中的Analyzer.java和Tokenizer.java中定義,Lucene提供了很多英文規(guī)范的實(shí)現(xiàn)樣本,也可以做為實(shí)現(xiàn)時(shí)候的參考資料。其次,需要按照被索引的文件的格式來提供相應(yīng)的文本分析邏輯,這里是指除開詞法分析之外的部分,比如HTML文件,通常需要把其中的內(nèi)容按照所屬于域分門別類加入索引,這就需要從org.apache.lucene.document中定義的類document繼承,定義自己的HTMLDocument類,然后就可以將之交給org.apache.lucene.index模塊來寫入索引文件。完成了這兩步之后,Lucene全文檢索引擎就基本上完備了。這個(gè)過程可以用下圖表示:
下面是使用java語言開發(fā),Lucene系統(tǒng)能夠方便的嵌入到整個(gè)系統(tǒng)中去,作為一個(gè)API集來調(diào)用。這個(gè)過程十分簡(jiǎn)單,以下便是一個(gè)示例程序,配合注釋理解起來很容易。
2.4 Lucene索引文件格式
首先在Lucene的文件格式中,以字節(jié)為基礎(chǔ),定義了如下的數(shù)據(jù)類型:
表 3.1 Lucene文件格式中定義的數(shù)據(jù)類型
數(shù)據(jù)類型
所占字節(jié)長(zhǎng)度(字節(jié))
說明
Byte
1
基本數(shù)據(jù)類型,其他數(shù)據(jù)類型以此為基礎(chǔ)定義
UInt32
4
32位無符號(hào)整數(shù),高位優(yōu)先
UInt64
8
64位無符號(hào)整數(shù),高位優(yōu)先
VInt
不定,最少1字節(jié)
動(dòng)態(tài)長(zhǎng)度整數(shù),每字節(jié)的最高位表明還剩多少字節(jié),每字節(jié)的低七位表明整數(shù)的值,高位優(yōu)先。可以認(rèn)為值可以為無限大。其示例如下
值
字節(jié)1
字節(jié)2
字節(jié)3
0
00000000
1
00000001
2
00000010
127
01111111
128
10000000
00000001
129
10000001
00000001
130
10000010
00000001
16383
10000000
10000000
00000001
16384
10000001
10000000
00000001
16385
10000010
10000000
00000001
Chars
不定,最少1字節(jié)
采用UTF-8編碼[20]的Unicode字符序列
String
不定,最少2字節(jié)
由VInt和Chars組成的字符串類型,VInt表示Chars的長(zhǎng)度,Chars則表示了String的值
以上的數(shù)據(jù)類型就是Lucene索引文件格式中用到的全部數(shù)據(jù)類型,由于它們都以字節(jié)為基礎(chǔ)定義而來,因此保證了是平臺(tái)無關(guān),這也是Lucene索引文件格式平臺(tái)無關(guān)的主要原因。接下來我們看看Lucene索引文件的概念組成和結(jié)構(gòu)組成。
以上就是Lucene的索引文件的概念結(jié)構(gòu)。Lucene索引index由若干段(segment)組成,每一段由若干的文檔(document)組成,每一個(gè)文檔由若干的域(field)組成,每一個(gè)域由若干的項(xiàng)(term)組成。項(xiàng)是最小的索引概念單位,它直接代表了一個(gè)字符串以及其在文件中的位置、出現(xiàn)次數(shù)等信息。域是一個(gè)關(guān)聯(lián)的元組,由一個(gè)域名和一個(gè)域值組成,域名是一個(gè)字串,域值是一個(gè)項(xiàng),比如將“標(biāo)題”和實(shí)際標(biāo)題的項(xiàng)組成的域。文檔是提取了某個(gè)文件中的所有信息之后的結(jié)果,這些組成了段,或者稱為一個(gè)子索引。子索引可以組合為索引,也可以合并為一個(gè)新的包含了所有合并項(xiàng)內(nèi)部元素的子索引。我們可以清楚的看出,Lucene的索引結(jié)構(gòu)在概念上即為傳統(tǒng)的倒排索引結(jié)構(gòu)。
從概念上映射到結(jié)構(gòu)中,索引被處理為一個(gè)目錄(文件夾),其中含有的所有文件即為其內(nèi)容,這些文件按照所屬的段不同分組存放,同組的文件擁有相同的文件名,不同的擴(kuò)展名。此外還有三個(gè)文件,分別用來保存所有的段的記錄、保存已刪除文件的記錄和控制讀寫的同步,它們分別是segments,deletable和lock文件,都沒有擴(kuò)展名。每個(gè)段包含一組文件,它們的文件擴(kuò)展名不同,但是文件名均為記錄在文件segments中段的名字。讓我們看如下的結(jié)構(gòu)圖3.2:
每個(gè)段的文件中,主要記錄了兩大類的信息:域集合與項(xiàng)集合。這兩個(gè)集合中所含有的文件在圖3.2中均有表明。由于索引信息是靜態(tài)存儲(chǔ)的,域集合與項(xiàng)集合中的文件組采用了一種類似的存儲(chǔ)辦法:一個(gè)小型的索引文件,運(yùn)行時(shí)載入內(nèi)存;一個(gè)對(duì)應(yīng)于索引文件的實(shí)際信息文件,可以按照索引中指示的偏移量隨機(jī)訪問;索引文件與信息文件在記錄的排列順序上存在隱式的對(duì)應(yīng)關(guān)系,即索引文件中按照“索引項(xiàng)1、索引項(xiàng)2…”排列,則信息文件則也按照“信息項(xiàng)1、信息項(xiàng)2…”排列。比如在圖3.2所示文件中,segment1.fdx與segment1.fdt之間,segment1.tii與segment1.tis、segment1.prx、segment1.frq之間,都存在這樣的組織關(guān)系。而域集合與項(xiàng)集合之間則通過域的在域記錄文件(比如segment1.fnm)中所記錄的域記錄號(hào)維持對(duì)應(yīng)關(guān)系,在圖3.2中segment1.fdx與segment1.tii中就是通過這種方式保持聯(lián)系。這樣,域集合和項(xiàng)集合不僅僅聯(lián)系起來,而且其中的文件之間也相互聯(lián)系起來。此外,標(biāo)準(zhǔn)化因子文件和被刪除文檔文件則提供了一些程序內(nèi)部的輔助設(shè)施(標(biāo)準(zhǔn)化因子用在評(píng)分排序機(jī)制中,被刪除文檔是一種偽刪除手段)。這樣,整個(gè)段的索引信息就通過這些文檔有機(jī)的組成。
2.5 一些公用的基礎(chǔ)類
基礎(chǔ)結(jié)構(gòu)封裝,或者基礎(chǔ)類,由org.apache.lucene.util和org.apache.lucene.document兩個(gè)包組成,前者定義了一些常量和優(yōu)化過的常用的數(shù)據(jù)結(jié)構(gòu)和算法,后者則是對(duì)于文檔(document)和域(field)概念的一個(gè)類定義。以下我們用列表的方式來分析這些封裝類,指出其要點(diǎn);
表 3.2 基礎(chǔ)類包org.apache.lucene.util
類
說明
Arrays
一個(gè)關(guān)于數(shù)組的排序方法的靜態(tài)類,提供了優(yōu)化的基于快排序的排序方法sort
BitVector
C/C++語言中位域的java實(shí)現(xiàn)品,但是加入了序列化能力
Constants
常量靜態(tài)類,定義了一些常量
PriorityQueue
一個(gè)優(yōu)先隊(duì)列的抽象類,用于后面實(shí)現(xiàn)各種具體的優(yōu)先隊(duì)列,提供常數(shù)時(shí)間內(nèi)的最小元素訪問能力,內(nèi)部實(shí)現(xiàn)機(jī)制是哈析表和堆排序算法
表 3.3 基礎(chǔ)類包org.apache.lucene.document
類
說明
Document
是文檔概念的一個(gè)實(shí)現(xiàn)類,每個(gè)文檔包含了一個(gè)域表(fieldList),并提供了一些實(shí)用的方法,比如多種添加域的方法、返回域表的迭代器的方法
Field
是域概念的一個(gè)實(shí)現(xiàn)類,每個(gè)域包含了一個(gè)域名和一個(gè)值,以及一些相關(guān)的屬性
DateField
提供了一些輔助方法的靜態(tài)類,這些方法將java中Date和Time數(shù)據(jù)類型和String相互轉(zhuǎn)化
2.6 存儲(chǔ)抽象
org.apache.lucene.store包:存儲(chǔ)抽象是唯一能夠直接對(duì)索引文件存取的包,因此其主要目的是抽象出和平臺(tái)文件系統(tǒng)無關(guān)的存儲(chǔ)抽象,提供諸如目錄服務(wù)(增、刪文件)、輸入流和輸出流。在分析其實(shí)現(xiàn)之前,首先我們看一下UML圖;
圖 3.3 存儲(chǔ)抽象實(shí)現(xiàn)UML圖(一)
圖 3.4 存儲(chǔ)抽象實(shí)現(xiàn)UML圖(二)
圖 3.4 存儲(chǔ)抽象實(shí)現(xiàn)UML圖(三)
圖3.2到3.4展示了整個(gè)org.apache.lucene.store中主要的繼承體系。共有三個(gè)抽象類定義:Directory、InputStream和OutputStrem,構(gòu)成了一個(gè)完整的基于抽象文件系統(tǒng)的存取體系結(jié)構(gòu),在此基礎(chǔ)上,實(shí)作出了兩個(gè)實(shí)現(xiàn)品:(FSDirectory,F(xiàn)SInputStream,F(xiàn)SOutputStream)和(RAMDirectory,RAMInputStream和RAMOutputStream)。前者是以實(shí)際的文件系統(tǒng)做為基礎(chǔ)實(shí)現(xiàn)的,后者則是建立在內(nèi)存中的虛擬文件系統(tǒng)。前者主要用來永久的保存索引文件,后者的作用則在于索引操作時(shí)是在內(nèi)存中建立小的索引,然后一次性的輸出合并到文件中去,這一點(diǎn)我們?cè)诤竺娴乃饕壿嫴糠帜軌蚩吹健4送猓€定以了org.apache.lucene.store.lock和org.apache.lucene.store.with兩個(gè)輔助內(nèi)部實(shí)現(xiàn)的類用在實(shí)現(xiàn)Directory方法的makeLock的時(shí)候,以在鎖定索引讀寫之前來讓客戶程序做一些準(zhǔn)備工作。
(FSDirectory,F(xiàn)SInputStream,F(xiàn)SOutputStream)的內(nèi)部實(shí)現(xiàn)依托于java語言中的io類庫,只是簡(jiǎn)單的做了一個(gè)外部邏輯的包裝。這當(dāng)然要?dú)w功于java語言所提供的跨平臺(tái)特性,同時(shí)也帶了一些隱患:文件存取的效率提升需要依耐于文件類庫的優(yōu)化。如果需要繼續(xù)優(yōu)化文件存取的效率,應(yīng)該還提供一個(gè)文件與目錄的抽象,以根據(jù)各種文件系統(tǒng)或者文件類型來提供一個(gè)優(yōu)化的機(jī)會(huì)。當(dāng)然,這是應(yīng)用開發(fā)者所不需要關(guān)系的問題。
(RAMDirectory,RAMInputStream和RAMOutputStream)的內(nèi)部實(shí)現(xiàn)就比較直接了,直接采用了虛擬的文件RAMFile類(定義于文件RAMDirectory.java中)來表示文件,目錄則看作一個(gè)String與RAMFile對(duì)應(yīng)的關(guān)聯(lián)數(shù)組。RAMFile中采用數(shù)組來表示文件的存儲(chǔ)空間。在此的基礎(chǔ)上,完成各項(xiàng)操作的實(shí)現(xiàn),就形成了基于內(nèi)存的虛擬文件系統(tǒng)。因?yàn)樵趯?shí)際使用時(shí),并不會(huì)牽涉到很大字節(jié)數(shù)量的文件,因此這種設(shè)計(jì)是簡(jiǎn)單直接的,也是高效率的。
3. Lucene索引構(gòu)建邏輯模塊分析
3.1對(duì)象體系與UML圖
1. 項(xiàng)(Term)
項(xiàng)(Term):包括概念所實(shí)際涉及的類、永久化類。項(xiàng)(Term)所表示的是一個(gè)字符串,它擁有域、頻數(shù)和位置信息等等屬性。因此,Lucene中設(shè)計(jì)了兩個(gè)類來表示這個(gè)概念,如下圖
圖 4.1 UML圖(-)
上圖中,有意的突出了類Term和TermInfo中的數(shù)據(jù)成員,因?yàn)樗从沉藢?duì)于項(xiàng)(Term)這個(gè)概念的具體表示。同時(shí)上圖中也同時(shí)列出了用于永久化項(xiàng)(Term)的代理類TermInfosWriter和TermInfosReader,它們完成永久化的功能,需要注意的是,TermInfosReader內(nèi)部使用了數(shù)組indexTerms和indexInfos來存儲(chǔ)一系列項(xiàng);而TermInfosWriter則是一個(gè)類似于鏈表的結(jié)構(gòu),通過一個(gè)other指向下一個(gè)TermInfosWriter,每一個(gè)TermInfosWriter只負(fù)責(zé)本身那個(gè)lastTerm和lastTi的永久化工作。這是一個(gè)設(shè)計(jì)上的技巧,通過批量讀取(或者稱為緩沖的方式)來獲得讀入時(shí)候的效率優(yōu)化;而通過一個(gè)鏈表式的、各負(fù)其責(zé)的方式,來獲得寫出時(shí)候的設(shè)計(jì)簡(jiǎn)化。
項(xiàng)(term)這部分的設(shè)計(jì)中,還有一些重要的接口和類:
圖 4.2 UML圖(二)
圖4.2中,我們看到三個(gè)類:TermEnum、TermDocs與TermPositions,第一個(gè)是抽象類,后兩個(gè)都是接口。TermEnum的設(shè)計(jì)主要用在后面Segment和Document等等的實(shí)現(xiàn)中,以提供枚舉其中每一個(gè)項(xiàng)(Term)的能力。TermDocs是一個(gè)接口,用來繼承以提供返回<document, frequency>值對(duì)的能力,通過這個(gè)接口就可以獲得某個(gè)項(xiàng)(Term)在某個(gè)文檔中出現(xiàn)的頻數(shù)。TermPositions則是在TermDocs上的擴(kuò)展,將項(xiàng)(Term)在文檔中的位置信息也表示出來。TermDocs(TermPositions)接口的使用方式類似于java中的Enumration接口,即通過next方法跳轉(zhuǎn),通過doc,freq等方法獲得當(dāng)前的屬性值。
2. 域(Field)
由于Field的基本概念在org.apache.lucene.document中已經(jīng)做了定義,因此在這部分主要是針對(duì)項(xiàng)文件(.fnm文件、.fdx文件、.fdt文件)所需要的信息再來設(shè)計(jì)一些類。
圖 4.3 UML圖(三)
圖 4.3中展示的,就是表示與域(Field)所關(guān)聯(lián)的屬性信息的類。其中isIndexed表示的這個(gè)域的值是否被索引過,即值是否被分詞然后索引;另外兩個(gè)屬性所表示的意思則很明顯:一個(gè)是域的名字,一個(gè)是域的編號(hào)。
關(guān)于域表和存取邏輯的UML圖:
FieldInfos即為域表的概念表示,內(nèi)部采用了冗余的方式以獲取在通過域的編號(hào)訪問或者通過域的名字來訪問時(shí)候的高效率。FieldsReader與FieldsWriter則分別是寫出和讀入的代理類。在功能和實(shí)現(xiàn)上,這兩個(gè)類都比較簡(jiǎn)單。
3. 文檔(document)
文檔(document)同樣也是在org.apache.lucene.document中定義過的結(jié)構(gòu)。由于對(duì)于這部分比較重要,我們也來看看其UML圖:
圖 4.5 UML圖(五)
在圖4.5中我們看到,Document的設(shè)計(jì)基本上沿用了鏈表的處理方法。左邊的Document類作為一個(gè)數(shù)據(jù)外包類,用來提供對(duì)于內(nèi)部結(jié)構(gòu)DocumentFieldList的增加刪除訪問操作等等。DocumentFieldList才是實(shí)際上的數(shù)據(jù)存儲(chǔ)單位,它用了鏈表的處理方法,直接指向一個(gè)當(dāng)前的Field對(duì)象和下一個(gè)DocumentFieldList對(duì)象,這個(gè)與前面的類似。為了能夠逐個(gè)訪問鏈表中的節(jié)點(diǎn),還設(shè)計(jì)了DocumentFieldEnumeration枚舉類。
圖 4.6 UML圖(六)
實(shí)際上定義于org.apache.lucene.index中的有關(guān)于Document的就是永久化的代理類。在圖4.6中給出了其UML圖。需要說明的是為什么沒有出現(xiàn)讀入的方法:這個(gè)方法已經(jīng)隱含在圖4.5中Document類中的add方法中了,結(jié)合圖2.4中的程序代碼段,我們就能夠清楚的理解這種設(shè)計(jì)。
4. 段(segment)
段(Segment)這一部分設(shè)計(jì)的比較特殊,在實(shí)現(xiàn)簡(jiǎn)單的對(duì)象結(jié)構(gòu)之上,還特意的設(shè)計(jì)了用于段之間合并的類。接下來,我們?nèi)匀徊扇?duì)照UML分析的方式逐個(gè)敘述。接下來我們看Lucene中如何表示段這個(gè)概念。
圖 4.7 UML圖(七)
Lucene定義了一個(gè)類SegmentInfo用來表示每一個(gè)段(Segment)的信息,包括名字(name)、含有的文檔的數(shù)目(docCount)和段所位于的目錄的位置(dir)。根據(jù)索引文件中的段的意義,有了這三點(diǎn),就能唯一確定一個(gè)段了。SegmentInfos這個(gè)類則是用來表示一個(gè)段的鏈表(從標(biāo)準(zhǔn)的java.util.Vector繼承而來),實(shí)際上,也就是索引(index)的意思了。需要注意的是,這里并沒有在SegmentInfo中安插一個(gè)文檔(document)的鏈表。這樣做的原因牽涉到Lucene內(nèi)部對(duì)于文檔(相當(dāng)于一個(gè)被索引文件)的處理;Lucene內(nèi)部采用了賦予文檔編號(hào),給域賦值的方式來處理文檔,即加入的文檔順次編號(hào),以后用文檔號(hào)表示文檔,而路徑信息,文件名字等等在以后索引查找需要的屬性,都作為域存儲(chǔ)下來;因此SegmentInfo中并沒有另外存儲(chǔ)一個(gè)文檔(document)的鏈表,對(duì)于這些的寫出和讀入,則交給了永久化的代理類來做。
圖 4.8 UML圖(八)
圖4.8給出了負(fù)責(zé)段(segment)的讀入操作的代理類,而負(fù)責(zé)段(segment)的寫出操作也同樣沒有定義,這些操作都直接實(shí)現(xiàn)在了類IndexWriter類中。段的操作同樣采用了之前的數(shù)組或者說是緩沖的處理方式。
針對(duì)前面項(xiàng)(term)那部分定義的幾個(gè)接口,段(segment)這部分也需要做相應(yīng)的接口實(shí)現(xiàn),因?yàn)樘峁┲苯颖闅v訪問段中的各個(gè)項(xiàng)的能力對(duì)于檢索來說,無疑是十分重要的。即這部分的設(shè)計(jì),實(shí)際上都是在為了檢索在服務(wù)。
圖 4.9 UML圖(九)
圖 4.10 UML圖(十)
圖4.9和圖4.10分別展示了前面項(xiàng)(term)那里定義的接口是如何在這里通過繼承實(shí)現(xiàn)的。Lucene在處理這部分的時(shí)候,也是分成兩部分(Segment與Segments開頭的類)來實(shí)現(xiàn),而且很合理的運(yùn)用了數(shù)組的技法,以及注意了繼承重用。但是細(xì)化到局部,終歸是比較簡(jiǎn)單的按照語義來獲得結(jié)果而已了。
Lucene為了兼顧建立索引時(shí)的效率和讀取索引查找的速度,引入了分小段建立索引的方式,即每一次批量建立索引時(shí),先在內(nèi)存中的虛擬文件系統(tǒng)中為每一個(gè)文檔單獨(dú)建立一個(gè)段,然后在輸出的時(shí)候?qū)⑦@些段合并之后輸出成為索引文件,這時(shí)僅僅存在一個(gè)段。多次建立的索引后,如果想優(yōu)化索引文件,也可采取合并段的方法,將索引中的段合并成為一個(gè)段。我們來看一下在IndexWriter類中相應(yīng)的方法的實(shí)現(xiàn),來了解一下這中建立索引的實(shí)現(xiàn)。
在mergeSegments函數(shù)中,將用到幾個(gè)重要的類結(jié)構(gòu),它們記錄了合并時(shí)候的一些重要信息,完成合并時(shí)候的工作。接下來,我們來看這幾個(gè)類的UML圖:
圖 4.12 UML圖(十一)
從圖4.12中,我們看到Lucene設(shè)計(jì)一個(gè)類SegmentMergeInfo用來保存每一個(gè)被合并的段的信息,也保存能夠訪問其內(nèi)部的接口句柄,也就是說合并時(shí)的操作使用這個(gè)類作為對(duì)被合并的段的操作代理。類SegmentMergeQueue則設(shè)計(jì)為org.apache.lucene.util.PriorityQueue的子類,做為SegmentMergeInfo的容器類,而且附帶能夠自動(dòng)排序。SegmentMerger是主要進(jìn)行操作的類,主要完成合并各個(gè)數(shù)據(jù)項(xiàng)的問題。
5. IndexReader類與IndexWirter類
最后剩下的,就是整個(gè)索引邏輯部分的使用接口類了。外界通過這兩個(gè)類以及文檔(document)類的構(gòu)造函數(shù)調(diào)用之,比如圖2.4中的代碼示例所示。下面我們來看一下這部分最后兩個(gè)類的UML圖:
圖 4.13 UML圖(十二)
IndexWriter的設(shè)計(jì)與IndexReader的設(shè)計(jì)很不相同,前者是一個(gè)實(shí)現(xiàn)類,而后者是一個(gè)抽象類,帶有沒有實(shí)現(xiàn)的接口。IndexWriter的主要作用就是接收新加入的文檔(document),然后在內(nèi)部為之生成相應(yīng)的小段,最后再合并并向索引文件中輸出,圖4.11中已經(jīng)給出了一些實(shí)現(xiàn)的代碼。由于Lucene在面向?qū)ο笊戏庋b的努力,通過各個(gè)構(gòu)造函數(shù)就已經(jīng)完成了對(duì)于各個(gè)概念的構(gòu)造過程,剩下部分的代碼主要是依據(jù)各個(gè)數(shù)組或者是鏈表中的信息,逐個(gè)逐個(gè)的將信息寫出到相應(yīng)的文件中去了。IndexReader部分則只是做了接口設(shè)計(jì),沒有具體的實(shí)現(xiàn),這個(gè)和本部分所完成的主要功能有關(guān):索引構(gòu)建邏輯。設(shè)計(jì)這個(gè)抽象類的目的是,預(yù)先完成一些函數(shù),為以后的檢索(search)部分的各種形式的IndexReader鋪平道路,也是利用了在同一個(gè)包內(nèi)可以方便訪問其它類的保護(hù)變量這個(gè)java語言的限制。
3.2 數(shù)據(jù)流邏輯
從宏觀上明白一個(gè)系統(tǒng)的設(shè)計(jì),理清楚其中的運(yùn)行規(guī)律,最好的方式應(yīng)該是通過數(shù)據(jù)流圖。在分析了各個(gè)位于索引構(gòu)建邏輯部分的類的設(shè)計(jì)之后,我們接下來就通過分析數(shù)據(jù)流圖的方式來總結(jié)一下。但是由于之前提到的原因:索引讀入部分在這一部分并沒有完全實(shí)現(xiàn),所以我們?cè)跀?shù)據(jù)流圖中主要給出的是索引構(gòu)建的數(shù)據(jù)流圖。
對(duì)于圖4.14中所描述的內(nèi)容,結(jié)合Lucene源代碼中的一些文件看,能夠加深理解。準(zhǔn)備階段可以參考demo文件夾中的org.apache.lucene.demo.IndexFiles類和java文件夾中的org.apache.lucene.document文件包。索引構(gòu)建階段的主要源碼位于java文件夾中org.apache.lucene.index.IndexWriter類,因此這部分可以結(jié)合這個(gè)類的實(shí)現(xiàn)來看。至于內(nèi)存文件系統(tǒng),比較復(fù)雜,但是這時(shí)的邏輯相對(duì)簡(jiǎn)單,因此也不難理解。
上面的數(shù)據(jù)流圖十分清楚的勾畫除了整個(gè)索引構(gòu)建邏輯這部分的設(shè)計(jì):通過層層嵌套的類結(jié)構(gòu),在構(gòu)建時(shí)候即分步驟有計(jì)劃的生成了索引結(jié)構(gòu),將之存儲(chǔ)到內(nèi)存中的文件系統(tǒng)中,然后通過對(duì)內(nèi)存中的文件系統(tǒng)優(yōu)化合并輸出到實(shí)際的文件系統(tǒng)中。
本文是在我2010年學(xué)習(xí)Lucene的時(shí)候在互聯(lián)網(wǎng)上摘抄整理而來,當(dāng)時(shí)是在一家電子商務(wù)公司做商品檢索需要用到Lucene,所以就研究了下。這篇文章也是在當(dāng)時(shí)在網(wǎng)絡(luò)上閱讀Lucene相關(guān)知識(shí)整理而來的。
作者:hoojo
出處:
blog:http://blog.csdn.net/IBM_hoojo
http://hoojo.cnblogs.com
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
版權(quán)所有,轉(zhuǎn)載請(qǐng)注明出處 本文出自:
