一、Lucene源碼實(shí)現(xiàn)分析的說(shuō)明
通過(guò)以上對(duì)Lucene系統(tǒng)結(jié)構(gòu)的分析,我們已經(jīng)大致的清楚了Lucene 系統(tǒng)的組成,以及在Lucene系統(tǒng)之上的開發(fā)步驟。接下來(lái),我們?cè)噲D來(lái)分析Lucene項(xiàng)目(采用Lucene 1.2版本)的源碼實(shí)現(xiàn),考察其實(shí)現(xiàn)的細(xì)節(jié)。這不僅僅是我們嘗試用C++語(yǔ)言重新實(shí)現(xiàn)Lucene的必須工作,也是進(jìn)一步做Lucene開發(fā)工作的必要準(zhǔn)備。因此,這一部分所涉及到的內(nèi)容,對(duì)于Lucene上的應(yīng)用開發(fā)也是有價(jià)值的,尤其是本部分所做的文件格式分析。
由于本文建立在我們的畢設(shè)項(xiàng)目之上,且同時(shí)我們需要實(shí)現(xiàn)cLucene項(xiàng)目,因此很遺憾的我們并沒(méi)有完全的完成Lucene的所有源碼實(shí)現(xiàn)的分析工作。接下來(lái)的部分,我們將涉及的部分為L(zhǎng)ucene文件格式分析,Lucene中的存儲(chǔ)抽象模塊分析,以及Lucene中的索引構(gòu)建邏輯模塊分析。這一部分,我們主要涉及到的是文件格式分析與存儲(chǔ)抽象模塊分析。
二、Lucene索引文件格式
在Lucene的web站點(diǎn)上,有關(guān)于 Lucene的文件格式的規(guī)范,其規(guī)定了Lucene的文件格式采取的存儲(chǔ)單位、組織結(jié)構(gòu)、命名規(guī)范等等內(nèi)容,但是它僅僅是一個(gè)規(guī)范說(shuō)明,并沒(méi)有從實(shí)現(xiàn)者角度來(lái)衡量這個(gè)規(guī)范的實(shí)現(xiàn)。因此,我們以下的內(nèi)容,結(jié)合了我們自己的分析與文件格式的定義規(guī)范,以期望給出一個(gè)更加清晰的文件格式說(shuō)明。具體的文檔規(guī)范可以參考后面的文獻(xiàn)2。
首先在Lucene的文件格式中,以字節(jié)為基礎(chǔ),定義了如下的數(shù)據(jù)類型:
表 3.1 Lucene文件格式中定義的數(shù)據(jù)類型
數(shù)據(jù)類型 |
所占字節(jié)長(zhǎng)度(字節(jié)) |
說(shuō)明 |
Byte |
1 |
基本數(shù)據(jù)類型,其他數(shù)據(jù)類型以此為基礎(chǔ)定義 |
UInt32 |
4 |
32位無(wú)符號(hào)整數(shù),高位優(yōu)先 |
UInt64 |
8 |
64位無(wú)符號(hào)整數(shù),高位優(yōu)先 |
VInt |
不定,最少1字節(jié) |
動(dòng)態(tài)長(zhǎng)度整數(shù),每字節(jié)的最高位表明還剩多少字節(jié),每字節(jié)的低七位表明整數(shù)的值,高位優(yōu)先??梢哉J(rèn)為值可以為無(wú)限大。其示例如下
值 |
字節(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ǔ)定義而來(lái),因此保證了是平臺(tái)無(wú)關(guān),這也是Lucene索引文件格式平臺(tái)無(wú)關(guān)的主要原因。接下來(lái)我們看看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)[21]。
從概念上映射到結(jié)構(gòu)中,索引被處理為一個(gè)目錄(文件夾),其中含有的所有文件即為其內(nèi)容,這些文件按照所屬的段不同分組存放,同組的文件擁有相同的文件名,不同的擴(kuò)展名。此外還有三個(gè)文件,分別用來(lái)保存所有的段的記錄、保存已刪除文件的記錄和控制讀寫的同步,它們分別是segments, deletable和lock文件,都沒(méi)有擴(kuò)展名。每個(gè)段包含一組文件,它們的文件擴(kuò)展名不同,但是文件名均為記錄在文件segments中段的名字。讓我們看如下的結(jié)構(gòu)圖3.2。
關(guān)于圖3.2中的各個(gè)文件具體的內(nèi)部格式,在參考文獻(xiàn)3中,均可以找到詳細(xì)的說(shuō)明。接下來(lái)我們從宏觀關(guān)系上說(shuō)明一下這些文件組成。在這些宏觀上的關(guān)系理清楚之后,仔細(xì)閱讀參考文獻(xiàn)3,即可清楚的明白具體的Lucene文件格式。
每個(gè)段的文件中,主要記錄了兩大類的信息:域集合與項(xiàng)集合。這兩個(gè)集合中所含有的文件在圖3.2中均有表明。由于索引信息是靜態(tài)存儲(chǔ)的,域集合與項(xiàng)集合中的文件組采用了一種類似的存儲(chǔ)辦法:一個(gè)小型的索引文件,運(yùn)行時(shí)載入內(nèi)存;一個(gè)對(duì)應(yīng)于索引文件的實(shí)際信息文件,可以按照索引中指示的偏移量隨機(jī)訪問(wèn);索引文件與信息文件在記錄的排列順序上存在隱式的對(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)集合之間則通過(guò)域的在域記錄文件(比如 segment1.fnm)中所記錄的域記錄號(hào)維持對(duì)應(yīng)關(guān)系,在圖3.2中segment1.fdx與segment1.tii中就是通過(guò)這種方式保持聯(lián)系。這樣,域集合和項(xiàng)集合不僅僅聯(lián)系起來(lái),而且其中的文件之間也相互聯(lián)系起來(lái)。此外,標(biāo)準(zhǔn)化因子文件和被刪除文檔文件則提供了一些程序內(nèi)部的輔助設(shè)施(標(biāo)準(zhǔn)化因子用在評(píng)分排序機(jī)制中,被刪除文檔是一種偽刪除手段)。這樣,整個(gè)段的索引信息就通過(guò)這些文檔有機(jī)的組成。
以上所闡述的,就是 Lucene所采用的索引文件格式?;旧隙?,它是一個(gè)倒排索引,但是Lucene在文件的安排上做了一些努力,比如使用索引/信息文件的方式,從文件安排的形式上提高查找的效率。這是一種數(shù)據(jù)庫(kù)之外的處理方法,其有其優(yōu)點(diǎn)(格式平**立、速度快),也有其缺點(diǎn)(獨(dú)立性帶來(lái)的共享訪問(wèn)接口問(wèn)題等等),具體如何衡量?jī)煞N方法之間的利弊,本文這里就不討論了。
三、一些公用的基礎(chǔ)類
分析完索引文件格式,我們接下來(lái)應(yīng)該著手對(duì)存儲(chǔ)抽象也就是org.apache.lucenestore中的源碼做一些分析。我們先不著急分析這部分,而是分析圖2.1中基礎(chǔ)結(jié)構(gòu)封裝那一部分,因?yàn)檫@是整個(gè)系統(tǒng)的基石,然后我們?cè)谙乱徊糠衷賮?lái)分析存儲(chǔ)抽象。
基礎(chǔ)結(jié)構(gòu)封裝,或者基礎(chǔ)類,由org.apache.lucene.util和org.apache.lucene.document兩個(gè)包組成,前者定義了一些常量和優(yōu)化過(guò)的常用的數(shù)據(jù)結(jié)構(gòu)和算法,后者則是對(duì)于文檔(document)和域(field)概念的一個(gè)類定義。以下我們用列表的方式來(lái)分析這些封裝類,指出其要點(diǎn)。
表 3.2 基礎(chǔ)類包org.apache.lucene.util
類 |
說(shuō)明 |
Arrays |
一個(gè)關(guān)于數(shù)組的排序方法的靜態(tài)類,提供了優(yōu)化的基于快排序的排序方法sort |
BitVector |
C/C++語(yǔ)言中位域的java實(shí)現(xiàn)品,但是加入了序列化能力 |
Constants |
常量靜態(tài)類,定義了一些常量 |
PriorityQueue |
一個(gè)優(yōu)先隊(duì)列的抽象類,用于后面實(shí)現(xiàn)各種具體的優(yōu)先隊(duì)列,提供常數(shù)時(shí)間內(nèi)的最小元素訪問(wèn)能力,內(nèi)部實(shí)現(xiàn)機(jī)制是哈析表和堆排序算法 |
表 3.3 基礎(chǔ)類包org.apache.lucene.document
類 |
說(shuō)明 |
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)化 |
總的來(lái)說(shuō),這兩個(gè)基礎(chǔ)類包中含有的類都比較簡(jiǎn)單,通過(guò)閱讀源代碼,可以很容易的理解,因此這里不作過(guò)多的展開。
四、存儲(chǔ)抽象
有了上面的知識(shí),我們接下來(lái)來(lái)分析存儲(chǔ)抽象部分,也就是org.apache.lucene.store包。存儲(chǔ)抽象是唯一能夠直接對(duì)索引文件存取的包,因此其主要目的是抽象出和平臺(tái)文件系統(tǒng)無(wú)關(guān)的存儲(chǔ)抽象,提供諸如目錄服務(wù)(增、刪文件)、輸入流和輸出流。在分析其實(shí)現(xiàn)之前,首先我們看一下UML [22]圖。

圖 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)。前者主要用來(lái)永久的保存索引文件,后者的作用則在于索引操作時(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í)候,以在鎖定索引讀寫之前來(lái)讓客戶程序做一些準(zhǔn)備工作。
?。‵SDirectory, FSInputStream,F(xiàn)SOutputStream)的內(nèi)部實(shí)現(xiàn)依托于java語(yǔ)言中的io類庫(kù),只是簡(jiǎn)單的做了一個(gè)外部邏輯的包裝。這當(dāng)然要?dú)w功于java語(yǔ)言所提供的跨平臺(tái)特性,同時(shí)也帶了一些隱患:文件存取的效率提升需要依耐于文件類庫(kù)的優(yōu)化。如果需要繼續(xù)優(yōu)化文件存取的效率,應(yīng)該還提供一個(gè)文件與目錄的抽象,以根據(jù)各種文件系統(tǒng)或者文件類型來(lái)提供一個(gè)優(yōu)化的機(jī)會(huì)。當(dāng)然,這是應(yīng)用開發(fā)者所不需要關(guān)系的問(wèn)題。
(RAMDirectory,RAMInputStream和RAMOutputStream)的內(nèi)部實(shí)現(xiàn)就比較直接了,直接采用了虛擬的文件 RAMFile類(定義于文件RAMDirectory.java中)來(lái)表示文件,目錄則看作一個(gè)String與RAMFile對(duì)應(yīng)的關(guān)聯(lián)數(shù)組。 RAMFile中采用數(shù)組來(lái)表示文件的存儲(chǔ)空間。在此的基礎(chǔ)上,完成各項(xiàng)操作的實(shí)現(xiàn),就形成了基于內(nèi)存的虛擬文件系統(tǒng)。因?yàn)樵趯?shí)際使用時(shí),并不會(huì)牽涉到很大字節(jié)數(shù)量的文件,因此這種設(shè)計(jì)是簡(jiǎn)單直接的,也是高效率的。
這部分的實(shí)現(xiàn)在理清楚繼承體系后,相當(dāng)?shù)暮?jiǎn)單。因此接下來(lái)的部分,我們可以通過(guò)直接閱讀源代碼解決。接下來(lái)我們看看這個(gè)部分的源代碼如何在實(shí)際中使用的。
一般來(lái)說(shuō),我們使用的是抽象類提供的接口而不是實(shí)際的實(shí)現(xiàn)類本身。在實(shí)現(xiàn)類中一般都含有幾個(gè)靜態(tài)函數(shù),比如createFile,它能夠返回一個(gè) OutputStream接口,或者openFile,它能夠返回一個(gè)InputStream接口,利用這些接口之中的方法,比如 writeString,writeByte等等,我們就能夠在抽象的層次上處理Lucene定義的數(shù)據(jù)類型的讀寫。簡(jiǎn)單的說(shuō),Lucene中存儲(chǔ)抽象這部分設(shè)計(jì)時(shí)采用了工廠模式(Factory parttern)[23]。我們利用靜態(tài)類的方法也就是工廠來(lái)創(chuàng)建對(duì)象,返回接口,通過(guò)接口來(lái)執(zhí)行操作。
五、關(guān)于cLucene項(xiàng)目
這一部分詳細(xì)的說(shuō)明了Lucene系統(tǒng)中所采用的索引文件格式、一些基礎(chǔ)類和存儲(chǔ)抽象。接下來(lái)我們來(lái)敘述一下我們?cè)陧?xiàng)目cLucene中重新實(shí)現(xiàn)這些結(jié)構(gòu)時(shí)候的一些考慮。
cLucene徹底的遵守了Lucene所定義的索引文件格式,這是Lucene對(duì)于各個(gè)兼容系統(tǒng)的基本要求。在此基礎(chǔ)上,cLucene系統(tǒng)和Lucene系統(tǒng)才能夠共享索引文件數(shù)據(jù)?;蛘哒f(shuō),cLucene生成的索引文件和Lucene生成的索引文件完全等價(jià)。
在基礎(chǔ)類問(wèn)題上,cLucene同樣封裝了類似的結(jié)構(gòu)。我們同樣列表描述,請(qǐng)和前面的表3.2與3.3對(duì)照比較。
表 3.4 基礎(chǔ)類包c(diǎn)Lucene::util
類 |
說(shuō)明 |
Arrays |
沒(méi)有實(shí)現(xiàn),直接利用了STL庫(kù)中的快排序算法實(shí)現(xiàn) |
BitVector |
C/C++語(yǔ)言版本的實(shí)現(xiàn),與java實(shí)現(xiàn)版本類似 |
Constants |
常量靜態(tài)類,定義了一些常量,但是與java版本不同的是,這里主要定義了一些宏 |
PriorityQueue |
這是一個(gè)類型定義,直接利用STL庫(kù)中的std::priority_queue |
表 3.3 基礎(chǔ)類包c(diǎn)Lucene::document
類 |
說(shuō)明 |
Document |
C/C++語(yǔ)言版本的實(shí)現(xiàn),與java實(shí)現(xiàn)版本類似 |
Field |
C/C++語(yǔ)言版本的實(shí)現(xiàn),與java實(shí)現(xiàn)版本類似 |
DateField |
沒(méi)有實(shí)現(xiàn),直接利用OpenTop庫(kù)中的ot::StringUtil |
存儲(chǔ)抽象的實(shí)現(xiàn)上,也同樣是類似于java實(shí)現(xiàn)。由于我們采用了OpenTop庫(kù),因此同樣得以借助其中對(duì)于文件系統(tǒng)抽象的ot::io包來(lái)解決文件系統(tǒng)問(wèn)題。這部分問(wèn)題與前面一樣,存在優(yōu)化的可能。在實(shí)現(xiàn)的類層次上、對(duì)外接口上,均與java版本的一樣。