1, 提高建索引的速度
/**
* 在IndexWriter中有一個(gè)MERGE_FACTOR參數(shù)可以幫助你在構(gòu)造索引器后根據(jù)應(yīng)用環(huán)境的情況充分利用內(nèi)存減少文件的操作。根據(jù)我的使用經(jīng) 驗(yàn):缺省Indexer是每20條記錄索引后寫(xiě)入一次,每將MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。
*/
indexWriter.setMergeFactor(1000);
2, 排序
« 從 漢化 到 國(guó)際化 | (回到Blog入口)|(回到首頁(yè)) | Resin學(xué)習(xí)筆記 »
Lucene:基于Java的全文檢索引擎簡(jiǎn)介
作者:車(chē)東 發(fā)表于:2002-08-06 18:08 最后更新于:2007-04-12 11:04
版權(quán)聲明:可以任意轉(zhuǎn)載,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明。
http://www.chedong.com/tech/lucene.html
--------------------------------------------------------------------------------
Lucene是一個(gè)基于Java的全文索引工具包。
基于Java的全文索引引擎Lucene簡(jiǎn)介:關(guān)于作者和Lucene的歷史
全文檢索的實(shí)現(xiàn):Luene全文索引和數(shù)據(jù)庫(kù)索引的比較
中文切分詞機(jī)制簡(jiǎn)介:基于詞庫(kù)和自動(dòng)切分詞算法的比較
具體的安裝和使用簡(jiǎn)介:系統(tǒng)結(jié)構(gòu)介紹和演示
Hacking Lucene:簡(jiǎn)化的查詢(xún)分析器,刪除的實(shí)現(xiàn),定制的排序,應(yīng)用接口的擴(kuò)展
從Lucene我們還可以學(xué)到什么
基于Java的全文索引/檢索引擎——Lucene
Lucene不是一個(gè)完整的全文索引應(yīng)用,而是是一個(gè)用Java寫(xiě)的全文索引引擎工具包,它可以方便的嵌入到各種應(yīng)用中實(shí)現(xiàn)針對(duì)應(yīng)用的全文索引/檢索功能。
Lucene的作者:Lucene的貢獻(xiàn)者Doug Cutting是一位資深全文索引/檢索專(zhuān)家,曾經(jīng)是V-Twin搜索引擎(Apple的Copland操作系統(tǒng)的成就之一)的主要開(kāi)發(fā)者,后在 Excite擔(dān)任高級(jí)系統(tǒng)架構(gòu)設(shè)計(jì)師,目前從事于一些INTERNET底層架構(gòu)的研究。他貢獻(xiàn)出的Lucene的目標(biāo)是為各種中小型應(yīng)用程序加入全文檢索 功能。
Lucene的發(fā)展歷程:早先發(fā)布在作者自己的www.lucene.com,后來(lái)發(fā)布在SourceForge,2001年年底成為APACHE基金會(huì) jakarta的一個(gè)子項(xiàng)目:http://jakarta.apache.org/lucene/
已經(jīng)有很多Java項(xiàng)目都使用了Lucene作為其后臺(tái)的全文索引引擎,比較著名的有:
Jive:WEB論壇系統(tǒng);
Eyebrows:郵件列表HTML歸檔/瀏覽/查詢(xún)系統(tǒng),本文的主要參考文檔“TheLucene search engine: Powerful, flexible, and free”作者就是EyeBrows系統(tǒng)的主要開(kāi)發(fā)者之一,而EyeBrows已經(jīng)成為目前APACHE項(xiàng)目的主要郵件列表歸檔系統(tǒng)。
Cocoon:基于XML的web發(fā)布框架,全文檢索部分使用了Lucene
Eclipse:基于Java的開(kāi)放開(kāi)發(fā)平臺(tái),幫助部分的全文索引使用了Lucene
對(duì)于中文用戶(hù)來(lái)說(shuō),最關(guān)心的問(wèn)題是其是否支持中文的全文檢索。但通過(guò)后面對(duì)于Lucene的結(jié)構(gòu)的介紹,你會(huì)了解到由于Lucene良好架構(gòu)設(shè)計(jì),對(duì)中文的支持只需對(duì)其語(yǔ)言詞法分析接口進(jìn)行擴(kuò)展就能實(shí)現(xiàn)對(duì)中文檢索的支持。
全文檢索的實(shí)現(xiàn)機(jī)制
Lucene的API接口設(shè)計(jì)的比較通用,輸入輸出結(jié)構(gòu)都很像數(shù)據(jù)庫(kù)的表==>記錄==>字段,所以很多傳統(tǒng)的應(yīng)用的文件、數(shù)據(jù)庫(kù)等都可以比 較方便的映射到Lucene的存儲(chǔ)結(jié)構(gòu)/接口中。總體上看:可以先把Lucene當(dāng)成一個(gè)支持全文索引的數(shù)據(jù)庫(kù)系統(tǒng)。
比較一下Lucene和數(shù)據(jù)庫(kù):
Lucene 數(shù)據(jù)庫(kù)
索引數(shù)據(jù)源:doc(field1,field2...) doc(field1,field2...) \ indexer / _____________ | Lucene Index| -------------- / searcher \ 結(jié)果輸出:Hits(doc(field1,field2) doc(field1...)) 索引數(shù)據(jù)源:record(field1,field2...) record(field1..) \ SQL: insert/ _____________ | DB Index | ------------- / SQL: select \結(jié)果輸出:results(record(field1,field2..) record(field1...))
Document:一個(gè)需要進(jìn)行索引的“單元”
一個(gè)Document由多個(gè)字段組成 Record:記錄,包含多個(gè)字段
Field:字段 Field:字段
Hits:查詢(xún)結(jié)果集,由匹配的Document組成 RecordSet:查詢(xún)結(jié)果集,由多個(gè)Record組成
全文檢索 ≠ like "%keyword%"
通常比較厚的書(shū)籍后面常常附關(guān)鍵詞索引表(比如:北京:12, 34頁(yè),上海:3,77頁(yè)……),它能夠幫助讀者比較快地找到相關(guān)內(nèi)容的頁(yè)碼。而數(shù)據(jù)庫(kù)索引能夠大大提高查詢(xún)的速度原理也是一樣,想像一下通過(guò)書(shū)后面的索 引查找的速度要比一頁(yè)一頁(yè)地翻內(nèi)容高多少倍……而索引之所以效率高,另外一個(gè)原因是它是排好序的。對(duì)于檢索系統(tǒng)來(lái)說(shuō)核心是一個(gè)排序問(wèn)題。
由于數(shù)據(jù)庫(kù)索引不是為全文索引設(shè)計(jì)的,因此,使用like "%keyword%"時(shí),數(shù)據(jù)庫(kù)索引是不起作用的,在使用like查詢(xún)時(shí),搜索過(guò)程又變成類(lèi)似于一頁(yè)頁(yè)翻書(shū)的遍歷過(guò)程了,所以對(duì)于含有模糊查詢(xún)的數(shù)據(jù)庫(kù) 服務(wù)來(lái)說(shuō),LIKE對(duì)性能的危害是極大的。如果是需要對(duì)多個(gè)關(guān)鍵詞進(jìn)行模糊匹配:like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。
所以建立一個(gè)高效檢索系統(tǒng)的關(guān)鍵是建立一個(gè)類(lèi)似于科技索引一樣的反向索引機(jī)制,將數(shù)據(jù)源(比如多篇文章)排序順序存儲(chǔ)的同時(shí),有另外一個(gè)排好序的關(guān)鍵詞列 表,用于存儲(chǔ)關(guān)鍵詞==>文章映射關(guān)系,利用這樣的映射關(guān)系索引:[關(guān)鍵詞==>出現(xiàn)關(guān)鍵詞的文章編號(hào),出現(xiàn)次數(shù)(甚至包括位置:起始偏移 量,結(jié)束偏移量),出現(xiàn)頻率],檢索過(guò)程就是把模糊查詢(xún)變成多個(gè)可以利用索引的精確查詢(xún)的邏輯組合的過(guò)程。從而大大提高了多關(guān)鍵詞查詢(xún)的效率,所以,全文 檢索問(wèn)題歸結(jié)到最后是一個(gè)排序問(wèn)題。
由此可以看出模糊查詢(xún)相對(duì)數(shù)據(jù)庫(kù)的精確查詢(xún)是一個(gè)非常不確定的問(wèn)題,這也是大部分?jǐn)?shù)據(jù)庫(kù)對(duì)全文檢索支持有限的原因。Lucene最核心的特征是通過(guò)特殊的 索引結(jié)構(gòu)實(shí)現(xiàn)了傳統(tǒng)數(shù)據(jù)庫(kù)不擅長(zhǎng)的全文索引機(jī)制,并提供了擴(kuò)展接口,以方便針對(duì)不同應(yīng)用的定制。
可以通過(guò)一下表格對(duì)比一下數(shù)據(jù)庫(kù)的模糊查詢(xún):
Lucene全文索引引擎 數(shù)據(jù)庫(kù)
索引 將數(shù)據(jù)源中的數(shù)據(jù)都通過(guò)全文索引一一建立反向索引 對(duì)于LIKE查詢(xún)來(lái)說(shuō),數(shù)據(jù)傳統(tǒng)的索引是根本用不上的。數(shù)據(jù)需要逐個(gè)便利記錄進(jìn)行GREP式的模糊匹配,比有索引的搜索速度要有多個(gè)數(shù)量級(jí)的下降。
匹配效果 通過(guò)詞元(term)進(jìn)行匹配,通過(guò)語(yǔ)言分析接口的實(shí)現(xiàn),可以實(shí)現(xiàn)對(duì)中文等非英語(yǔ)的支持。 使用:like "%net%" 會(huì)把netherlands也匹配出來(lái),
多個(gè)關(guān)鍵詞的模糊匹配:使用like "%com%net%":就不能匹配詞序顛倒的xxx.net..xxx.com
匹配度 有匹配度算法,將匹配程度(相似度)比較高的結(jié)果排在前面。 沒(méi)有匹配程度的控制:比如有記錄中net出現(xiàn)5詞和出現(xiàn)1次的,結(jié)果是一樣的。
結(jié)果輸出 通過(guò)特別的算法,將最匹配度最高的頭100條結(jié)果輸出,結(jié)果集是緩沖式的小批量讀取的。 返回所有的結(jié)果集,在匹配條目非常多的時(shí)候(比如上萬(wàn)條)需要大量的內(nèi)存存放這些臨時(shí)結(jié)果集。
可定制性 通過(guò)不同的語(yǔ)言分析接口實(shí)現(xiàn),可以方便的定制出符合應(yīng)用需要的索引規(guī)則(包括對(duì)中文的支持) 沒(méi)有接口或接口復(fù)雜,無(wú)法定制
結(jié)論 高負(fù)載的模糊查詢(xún)應(yīng)用,需要負(fù)責(zé)的模糊查詢(xún)的規(guī)則,索引的資料量比較大 使用率低,模糊匹配規(guī)則簡(jiǎn)單或者需要模糊查詢(xún)的資料量少
全文檢索和數(shù)據(jù)庫(kù)應(yīng)用最大的不同在于:讓最相關(guān)的頭100條結(jié)果滿(mǎn)足98%以上用戶(hù)的需求
Lucene的創(chuàng)新之處:
大部分的搜索(數(shù)據(jù)庫(kù))引擎都是用B樹(shù)結(jié)構(gòu)來(lái)維護(hù)索引,索引的更新會(huì)導(dǎo)致大量的IO操作,Lucene在實(shí)現(xiàn)中,對(duì)此稍微有所改進(jìn):不是維護(hù)一個(gè)索引文 件,而是在擴(kuò)展索引的時(shí)候不斷創(chuàng)建新的索引文件,然后定期的把這些新的小索引文件合并到原先的大索引中(針對(duì)不同的更新策略,批次的大小可以調(diào)整),這樣 在不影響檢索的效率的前提下,提高了索引的效率。
Lucene和其他一些全文檢索系統(tǒng)/應(yīng)用的比較:
Lucene 其他開(kāi)源全文檢索系統(tǒng)
增量索引和批量索引 可以進(jìn)行增量的索引(Append),可以對(duì)于大量數(shù)據(jù)進(jìn)行批量索引,并且接口設(shè)計(jì)用于優(yōu)化批量索引和小批量的增量索引。 很多系統(tǒng)只支持批量的索引,有時(shí)數(shù)據(jù)源有一點(diǎn)增加也需要重建索引。
數(shù)據(jù)源 Lucene沒(méi)有定義具體的數(shù)據(jù)源,而是一個(gè)文檔的結(jié)構(gòu),因此可以非常靈活的適應(yīng)各種應(yīng)用(只要前端有合適的轉(zhuǎn)換器把數(shù)據(jù)源轉(zhuǎn)換成相應(yīng)結(jié)構(gòu)), 很多系統(tǒng)只針對(duì)網(wǎng)頁(yè),缺乏其他格式文檔的靈活性。
索引內(nèi)容抓取 Lucene的文檔是由多個(gè)字段組成的,甚至可以控制那些字段需要進(jìn)行索引,那些字段不需要索引,近一步索引的字段也分為需要分詞和不需要分詞的類(lèi)型:
需要進(jìn)行分詞的索引,比如:標(biāo)題,文章內(nèi)容字段
不需要進(jìn)行分詞的索引,比如:作者/日期字段 缺乏通用性,往往將文檔整個(gè)索引了
語(yǔ)言分析 通過(guò)語(yǔ)言分析器的不同擴(kuò)展實(shí)現(xiàn):
可以過(guò)濾掉不需要的詞:an the of 等,
西文語(yǔ)法分析:將jumps jumped jumper都?xì)w結(jié)成jump進(jìn)行索引/檢索
非英文支持:對(duì)亞洲語(yǔ)言,阿拉伯語(yǔ)言的索引支持 缺乏通用接口實(shí)現(xiàn)
查詢(xún)分析 通過(guò)查詢(xún)分析接口的實(shí)現(xiàn),可以定制自己的查詢(xún)語(yǔ)法規(guī)則:
比如: 多個(gè)關(guān)鍵詞之間的 + - and or關(guān)系等
并發(fā)訪(fǎng)問(wèn) 能夠支持多用戶(hù)的使用
關(guān)于亞洲語(yǔ)言的的切分詞問(wèn)題(Word Segment)
對(duì)于中文來(lái)說(shuō),全文索引首先還要解決一個(gè)語(yǔ)言分析的問(wèn)題,對(duì)于英文來(lái)說(shuō),語(yǔ)句中單詞之間是天然通過(guò)空格分開(kāi)的,但亞洲語(yǔ)言的中日韓文語(yǔ)句中的字是一個(gè)字挨一個(gè),所有,首先要把語(yǔ)句中按“詞”進(jìn)行索引的話(huà),這個(gè)詞如何切分出來(lái)就是一個(gè)很大的問(wèn)題。
首先,肯定不能用單個(gè)字符作(si-gram)為索引單元,否則查“上海”時(shí),不能讓含有“海上”也匹配。
但一句話(huà):“北京天安門(mén)”,計(jì)算機(jī)如何按照中文的語(yǔ)言習(xí)慣進(jìn)行切分呢?
“北京 天安門(mén)” 還是“北 京 天安門(mén)”?讓計(jì)算機(jī)能夠按照語(yǔ)言習(xí)慣進(jìn)行切分,往往需要機(jī)器有一個(gè)比較豐富的詞庫(kù)才能夠比較準(zhǔn)確的識(shí)別出語(yǔ)句中的單詞。
另外一個(gè)解決的辦法是采用自動(dòng)切分算法:將單詞按照2元語(yǔ)法(bigram)方式切分出來(lái),比如:
"北京天安門(mén)" ==> "北京 京天 天安 安門(mén)"。
這樣,在查詢(xún)的時(shí)候,無(wú)論是查詢(xún)"北京" 還是查詢(xún)"天安門(mén)",將查詢(xún)?cè)~組按同樣的規(guī)則進(jìn)行切分:"北京","天安安門(mén)",多個(gè)關(guān)鍵詞之間按與"and"的關(guān)系組合,同樣能夠正確地映射到相應(yīng)的索 引中。這種方式對(duì)于其他亞洲語(yǔ)言:韓文,日文都是通用的。
基于自動(dòng)切分的最大優(yōu)點(diǎn)是沒(méi)有詞表維護(hù)成本,實(shí)現(xiàn)簡(jiǎn)單,缺點(diǎn)是索引效率低,但對(duì)于中小型應(yīng)用來(lái)說(shuō),基于2元語(yǔ)法的切分還是夠用的。基于2元切分后的索引一般大小和源文件差不多,而對(duì)于英文,索引文件一般只有原文件的30%-40%不同,
自動(dòng)切分 詞表切分
實(shí)現(xiàn) 實(shí)現(xiàn)非常簡(jiǎn)單 實(shí)現(xiàn)復(fù)雜
查詢(xún) 增加了查詢(xún)分析的復(fù)雜程度, 適于實(shí)現(xiàn)比較復(fù)雜的查詢(xún)語(yǔ)法規(guī)則
存儲(chǔ)效率 索引冗余大,索引幾乎和原文一樣大 索引效率高,為原文大小的30%左右
維護(hù)成本 無(wú)詞表維護(hù)成本 詞表維護(hù)成本非常高:中日韓等語(yǔ)言需要分別維護(hù)。
還需要包括詞頻統(tǒng)計(jì)等內(nèi)容
適用領(lǐng)域 嵌入式系統(tǒng):運(yùn)行環(huán)境資源有限
分布式系統(tǒng):無(wú)詞表同步問(wèn)題
多語(yǔ)言環(huán)境:無(wú)詞表維護(hù)成本 對(duì)查詢(xún)和存儲(chǔ)效率要求高的專(zhuān)業(yè)搜索引擎
目前比較大的搜索引擎的語(yǔ)言分析算法一般是基于以上2個(gè)機(jī)制的結(jié)合。關(guān)于中文的語(yǔ)言分析算法,大家可以在Google查關(guān)鍵詞"wordsegment search"能找到更多相關(guān)的資料。
安裝和使用
下載:http://jakarta.apache.org/lucene/
注意:Lucene中的一些比較復(fù)雜的詞法分析是用JavaCC生成的(JavaCC:JavaCompilerCompiler,純Java的詞法分析 生成器),所以如果從源代碼編譯或需要修改其中的QueryParser、定制自己的詞法分析器,還需要從 https://javacc.dev.java.net/下載javacc。
lucene的組成結(jié)構(gòu):對(duì)于外部應(yīng)用來(lái)說(shuō)索引模塊(index)和檢索模塊(search)是主要的外部應(yīng)用入口
org.apache.Lucene.search/ 搜索入口
org.apache.Lucene.index/ 索引入口
org.apache.Lucene.analysis/ 語(yǔ)言分析器
org.apache.Lucene.queryParser/ 查詢(xún)分析器
org.apache.Lucene.document/ 存儲(chǔ)結(jié)構(gòu)
org.apache.Lucene.store/ 底層IO/存儲(chǔ)結(jié)構(gòu)
org.apache.Lucene.util/ 一些公用的數(shù)據(jù)結(jié)構(gòu)
簡(jiǎn)單的例子演示一下Lucene的使用方法:
索引過(guò)程:從命令行讀取文件名(多個(gè)),將文件分路徑(path字段)和內(nèi)容(body字段)2個(gè)字段進(jìn)行存儲(chǔ),并對(duì)內(nèi)容進(jìn)行全文索引:索引的單位是 Document對(duì)象,每個(gè)Document對(duì)象包含多個(gè)字段Field對(duì)象,針對(duì)不同的字段屬性和數(shù)據(jù)輸出的需求,對(duì)字段還可以選擇不同的索引/存儲(chǔ)字 段規(guī)則,列表如下: 方法 切詞 索引 存儲(chǔ) 用途
Field.Text(String name, String value) Yes Yes Yes 切分詞索引并存儲(chǔ),比如:標(biāo)題,內(nèi)容字段
Field.Text(String name, Reader value) Yes Yes No 切分詞索引不存儲(chǔ),比如:META信息,
不用于返回顯示,但需要進(jìn)行檢索內(nèi)容
Field.Keyword(String name, String value) No Yes Yes 不切分索引并存儲(chǔ),比如:日期字段
Field.UnIndexed(String name, String value) No No Yes 不索引,只存儲(chǔ),比如:文件路徑
Field.UnStored(String name, String value) Yes Yes No 只全文索引,不存儲(chǔ)
public class IndexFiles { //使用方法:: IndexFiles [索引輸出目錄](méi) [索引的文件列表] ... public static void main(String[] args) throws Exception { String indexPath = args[0]; IndexWriter writer; //用指定的語(yǔ)言分析器構(gòu)造一個(gè)新的寫(xiě)索引器(第3個(gè)參數(shù)表示是否為追加索引) writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false); for (int i=1; i<args.length; i++) { System.out.println("Indexing file " + args); InputStream is = new FileInputStream(args); //構(gòu)造包含2個(gè)字段Field的Document對(duì)象 //一個(gè)是路徑path字段,不索引,只存儲(chǔ) //一個(gè)是內(nèi)容body字段,進(jìn)行全文索引,并存儲(chǔ) Document doc = new Document(); doc.add(Field.UnIndexed("path", args)); doc.add(Field.Text("body", (Reader) new InputStreamReader(is))); //將文檔寫(xiě)入索引 writer.addDocument(doc); is.close(); }; //關(guān)閉寫(xiě)索引器 writer.close(); }} 索引過(guò)程中可以看到:
語(yǔ)言分析器提供了抽象的接口,因此語(yǔ)言分析(Analyser)是可以定制的,雖然lucene缺省提供了2個(gè)比較通用的分析器 SimpleAnalyser和StandardAnalyser,這2個(gè)分析器缺省都不支持中文,所以要加入對(duì)中文語(yǔ)言的切分規(guī)則,需要修改這2個(gè)分析 器。
Lucene并沒(méi)有規(guī)定數(shù)據(jù)源的格式,而只提供了一個(gè)通用的結(jié)構(gòu)(Document對(duì)象)來(lái)接受索引的輸入,因此輸入的數(shù)據(jù)源可以是:數(shù)據(jù)庫(kù),WORD文 檔,PDF文檔,HTML文檔……只要能夠設(shè)計(jì)相應(yīng)的解析轉(zhuǎn)換器將數(shù)據(jù)源構(gòu)造成成Docuement對(duì)象即可進(jìn)行索引。
對(duì)于大批量的數(shù)據(jù)索引,還可以通過(guò)調(diào)整IndexerWrite的文件合并頻率屬性(mergeFactor)來(lái)提高批量索引的效率。
檢索過(guò)程和結(jié)果顯示:
搜索結(jié)果返回的是Hits對(duì)象,可以通過(guò)它再訪(fǎng)問(wèn)Document==>Field中的內(nèi)容。
假設(shè)根據(jù)body字段進(jìn)行全文檢索,可以將查詢(xún)結(jié)果的path字段和相應(yīng)查詢(xún)的匹配度(score)打印出來(lái),
public class Search { public static void main(String[] args) throws Exception { String indexPath = args[0], queryString = args[1]; //指向索引目錄的搜索器 Searcher searcher = new IndexSearcher(indexPath); //查詢(xún)解析器:使用和索引同樣的語(yǔ)言分析器 Query query = QueryParser.parse(queryString, "body", new SimpleAnalyzer()); //搜索結(jié)果使用Hits存儲(chǔ) Hits hits = searcher.search(query); //通過(guò)hits可以訪(fǎng)問(wèn)到相應(yīng)字段的數(shù)據(jù)和查詢(xún)的匹配度 for (int i=0; i<hits.length(); i++) { System.out.println(hits.doc(i).get("path") + "; Score: " + hits.score(i)); }; }}在整個(gè)檢索過(guò)程中,語(yǔ)言分析器,查詢(xún)分析器,甚至搜索器(Searcher)都是提供了抽象的接口,可以根據(jù)需要進(jìn)行定制。
Hacking Lucene
簡(jiǎn)化的查詢(xún)分析器
個(gè)人感覺(jué)lucene成為JAKARTA項(xiàng)目后,畫(huà)在了太多的時(shí)間用于調(diào)試日趨復(fù)雜QueryParser,而其中大部分是大多數(shù)用戶(hù)并不很熟悉的,目前LUCENE支持的語(yǔ)法:
Query ::= ( Clause )*
Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")
中間的邏輯包括:and or + - &&||等符號(hào),而且還有"短語(yǔ)查詢(xún)"和針對(duì)西文的前綴/模糊查詢(xún)等,個(gè)人感覺(jué)對(duì)于一般應(yīng)用來(lái)說(shuō),這些功能有一些華而不實(shí),其實(shí)能夠?qū)崿F(xiàn) 目前類(lèi)似于Google的查詢(xún)語(yǔ)句分析功能其實(shí)對(duì)于大多數(shù)用戶(hù)來(lái)說(shuō)已經(jīng)夠了。所以,Lucene早期版本的QueryParser仍是比較好的選擇。
添加修改刪除指定記錄(Document)
Lucene提供了索引的擴(kuò)展機(jī)制,因此索引的動(dòng)態(tài)擴(kuò)展應(yīng)該是沒(méi)有問(wèn)題的,而指定記錄的修改也似乎只能通過(guò)記錄的刪除,然后重新加入實(shí)現(xiàn)。如何刪除指定的 記錄呢?刪除的方法也很簡(jiǎn)單,只是需要在索引時(shí)根據(jù)數(shù)據(jù)源中的記錄ID專(zhuān)門(mén)另建索引,然后利用IndexReader.delete(Termterm) 方法通過(guò)這個(gè)記錄ID刪除相應(yīng)的Document。
根據(jù)某個(gè)字段值的排序功能
lucene缺省是按照自己的相關(guān)度算法(score)進(jìn)行結(jié)果排序的,但能夠根據(jù)其他字段進(jìn)行結(jié)果排序是一個(gè)在LUCENE的開(kāi)發(fā)郵件列表中經(jīng)常提到的 問(wèn)題,很多原先基于數(shù)據(jù)庫(kù)應(yīng)用都需要除了基于匹配度(score)以外的排序功能。而從全文檢索的原理我們可以了解到,任何不基于索引的搜索過(guò)程效率都會(huì) 導(dǎo)致效率非常的低,如果基于其他字段的排序需要在搜索過(guò)程中訪(fǎng)問(wèn)存儲(chǔ)字段,速度回大大降低,因此非常是不可取的。
但這里也有一個(gè)折中的解決方法:在搜索過(guò)程中能夠影響排序結(jié)果的只有索引中已經(jīng)存儲(chǔ)的docID和score這2個(gè)參數(shù),所以,基于score以外的排 序,其實(shí)可以通過(guò)將數(shù)據(jù)源預(yù)先排好序,然后根據(jù)docID進(jìn)行排序來(lái)實(shí)現(xiàn)。這樣就避免了在LUCENE搜索結(jié)果外對(duì)結(jié)果再次進(jìn)行排序和在搜索過(guò)程中訪(fǎng)問(wèn)不 在索引中的某個(gè)字段值。
這里需要修改的是IndexSearcher中的HitCollector過(guò)程:
... scorer.score(new HitCollector() { private float minScore = 0.0f; public final void collect(int doc, float score) { if (score > 0.0f && // ignore zeroed buckets (bits==null || bits.get(doc))) { // skip docs not in bits totalHits[0]++; if (score >= minScore) { /* 原先:Lucene將docID和相應(yīng)的匹配度score例入結(jié)果命中列表中: * hq.put(new ScoreDoc(doc, score)); // update hit queue * 如果用doc 或 1/doc 代替 score,就實(shí)現(xiàn)了根據(jù)docID順排或逆排 * 假設(shè)數(shù)據(jù)源索引時(shí)已經(jīng)按照某個(gè)字段排好了序,而結(jié)果根據(jù)docID排序也就實(shí)現(xiàn)了 * 針對(duì)某個(gè)字段的排序,甚至可以實(shí)現(xiàn)更復(fù)雜的score和docID的擬合。 */ hq.put(new ScoreDoc(doc, (float) 1/doc )); if (hq.size() > nDocs) { // if hit queue overfull hq.pop(); // remove lowest in hit queue minScore = ((ScoreDoc)hq.top()).score; // reset minScore } } } } }, reader.maxDoc());
3, 計(jì)算匹配得分. 小于1.0的我們認(rèn)為是相關(guān)的記錄了.下面的代碼在輸出結(jié)果循環(huán)中. 如果要獲取完全匹配的記錄,
//計(jì)算匹配得分
Explanation explanation = searcher.explain(query, hits.id(i)) ;
System.out.println("匹配得分:"+explanation.getValue());
System.out.println("=========");
4, 關(guān)于短語(yǔ)匹配的用法
通過(guò)短語(yǔ)搜索:PhraseQuery
索引中包含了各個(gè)項(xiàng)的位置信息。PhraseQuery利用這些信息去搜索文檔,在這些文檔集中,我們所查找的各個(gè)項(xiàng)之間可能都相隔著一定的距離。例如, 假設(shè)某個(gè)域中包含了“the quick brown fox jumped over the lazy dog”這個(gè)短語(yǔ),即使我們不知道這個(gè)短語(yǔ)的確切完整寫(xiě)法,也一樣可以通過(guò)查找域中quick和fox距離相近的文檔來(lái)找出我們需要的文檔。當(dāng)然,一個(gè)簡(jiǎn) 單的TermQuery也能夠通過(guò)對(duì)這兩個(gè)項(xiàng)的單獨(dú)查詢(xún)成功地找到同樣文檔;但是在以上所討論的情況中,我們僅僅希望查到域中quick的位置緊挨著 fox或者隔一個(gè)不相關(guān)的單詞的文檔(如quick [不相關(guān)的詞] fox)。
在匹配的情況下,兩個(gè)項(xiàng)的位置之間允許的最大間隔距離稱(chēng)為slop。距離是指項(xiàng)要按順序組成給定的短語(yǔ),所需要移動(dòng)位置的次數(shù)。我們用剛剛提到的那個(gè)短 語(yǔ),看看slop因子是怎么樣工作的。首先,我們需要構(gòu)建一個(gè)小的基本測(cè)試構(gòu)架,程序里用一個(gè)setUp()方法來(lái)索引一個(gè)文檔,并通過(guò) matched(String[], int)方法構(gòu)造、執(zhí)行并斷言一個(gè)短語(yǔ)查詢(xún)與我們的測(cè)試文檔相匹配:
// 建立樣本文檔
由于只想示范一下幾個(gè)短語(yǔ)查詢(xún)的例子,因此在以上程序中我們簡(jiǎn)化了matched方法的代碼。這個(gè)程序按照一定的順序添加各個(gè)項(xiàng)來(lái)進(jìn)行短語(yǔ)查詢(xún)。默認(rèn)情況 下,PhraseQuery的slop因子設(shè)置為0,即要求查詢(xún)的結(jié)果必須和我們輸入的字符串組完全精確一致地匹配。通過(guò)setUp()和 matched()方法,測(cè)試用例對(duì)PhraseQuery的工作方式做出了簡(jiǎn)潔的示范。程序以查詢(xún)失敗或超出slop因子作為其邊界:
圖3.2 解釋短語(yǔ)查詢(xún)slop因子:短語(yǔ)“quick fox”需要slop值為1的移動(dòng)才能和原文檔匹配,而“fox quick”需要slop值為3的移動(dòng)才能匹配
在短語(yǔ)查詢(xún)中,雖然項(xiàng)出現(xiàn)的先后順序會(huì)對(duì)slop因子的選取有一定影響,但是我們不一定需要按照這些項(xiàng)在文檔中出現(xiàn)的先后順序來(lái)將它們添加至 PhraseQuery中。例如,如果把上述String數(shù)組中的兩個(gè)項(xiàng)顛倒(先是項(xiàng)“fox”,然后是“quick”),要和文檔匹配就需要移動(dòng)三個(gè)位 置,而不是原先的一個(gè)了。為了表達(dá)得更形象一些,可以思考一下單詞“fox”需要移動(dòng)多少個(gè)位置才能位于單詞“quick”的兩個(gè)位置之后。你會(huì)發(fā)現(xiàn) fox移動(dòng)一次到達(dá)quick的位置,然后再移動(dòng)兩次才能使之變成“quick X fox”,從而和“quick brown fox”充分地匹配。
圖3.2展示了slop位置因子在這兩個(gè)短語(yǔ)查詢(xún)場(chǎng)景的應(yīng)用是如何工作的,下面的測(cè)試用例示范了程序如何通過(guò)slop因子的設(shè)置實(shí)現(xiàn)對(duì)String[] {"fox", "quick"}的匹配:
現(xiàn)在我們開(kāi)始深入學(xué)習(xí)如何對(duì)多個(gè)項(xiàng)進(jìn)行復(fù)合查詢(xún)的問(wèn)題。
復(fù)合項(xiàng)短語(yǔ)
PhraseQuery支持復(fù)合項(xiàng)短語(yǔ)(multiple-term phrases)。不管短語(yǔ)中有多少個(gè)項(xiàng),slop因子都規(guī)定了項(xiàng)按順序移動(dòng)位置的所允許的最大值。下面看看關(guān)于復(fù)合項(xiàng)短語(yǔ)查詢(xún)的一個(gè)示例:
到目前為止,你已經(jīng)了解了短語(yǔ)查詢(xún)是如何進(jìn)行匹配的,下面我們把注意力轉(zhuǎn)向于短語(yǔ)查詢(xún)對(duì)文檔評(píng)分的影響。
短語(yǔ)查詢(xún)?cè)u(píng)分
短語(yǔ)查詢(xún)是根據(jù)短語(yǔ)匹配所需要的編輯距離來(lái)進(jìn)行評(píng)分的。項(xiàng)之間距離越小的匹配具有的權(quán)重也就越大。短語(yǔ)查詢(xún)的評(píng)分因子如圖3.3所示。評(píng)分與距離成反比關(guān)系,距離越大的匹配其評(píng)分越低。
圖3.3 短語(yǔ)查詢(xún)的評(píng)分公式
注:在QueryParser的分析表達(dá)式中雙引號(hào)里的若干個(gè)項(xiàng)被被轉(zhuǎn)換為一個(gè)PhraseQuery對(duì)象。Slop因子的默認(rèn)值是0,但是你可以在 QueryParser的查詢(xún)表達(dá)式中加上~n的聲明,以此來(lái)調(diào)整slop因子的值。例如,表達(dá)式“quick fox”~3的意義為:為fox和quick項(xiàng)生成一個(gè)slop因子為3的PhraseQuery對(duì)象。更多關(guān)于PhraseQuery和slop因子的 細(xì)節(jié)請(qǐng)參看3.5.6小節(jié)。短語(yǔ)由傳給QueryParser的分析器進(jìn)行分析,在此過(guò)程中還會(huì)加入另外一個(gè)復(fù)雜的層,這個(gè)內(nèi)容將會(huì)在4.1.2小節(jié)中加 以討論。
ExtJS教程- Hibernate教程-Struts2 教程-Lucene教程
/**
* 在IndexWriter中有一個(gè)MERGE_FACTOR參數(shù)可以幫助你在構(gòu)造索引器后根據(jù)應(yīng)用環(huán)境的情況充分利用內(nèi)存減少文件的操作。根據(jù)我的使用經(jīng) 驗(yàn):缺省Indexer是每20條記錄索引后寫(xiě)入一次,每將MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。
*/
indexWriter.setMergeFactor(1000);
2, 排序
« 從 漢化 到 國(guó)際化 | (回到Blog入口)|(回到首頁(yè)) | Resin學(xué)習(xí)筆記 »
Lucene:基于Java的全文檢索引擎簡(jiǎn)介
作者:車(chē)東 發(fā)表于:2002-08-06 18:08 最后更新于:2007-04-12 11:04
版權(quán)聲明:可以任意轉(zhuǎn)載,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明。
http://www.chedong.com/tech/lucene.html
--------------------------------------------------------------------------------
Lucene是一個(gè)基于Java的全文索引工具包。
基于Java的全文索引引擎Lucene簡(jiǎn)介:關(guān)于作者和Lucene的歷史
全文檢索的實(shí)現(xiàn):Luene全文索引和數(shù)據(jù)庫(kù)索引的比較
中文切分詞機(jī)制簡(jiǎn)介:基于詞庫(kù)和自動(dòng)切分詞算法的比較
具體的安裝和使用簡(jiǎn)介:系統(tǒng)結(jié)構(gòu)介紹和演示
Hacking Lucene:簡(jiǎn)化的查詢(xún)分析器,刪除的實(shí)現(xiàn),定制的排序,應(yīng)用接口的擴(kuò)展
從Lucene我們還可以學(xué)到什么
基于Java的全文索引/檢索引擎——Lucene
Lucene不是一個(gè)完整的全文索引應(yīng)用,而是是一個(gè)用Java寫(xiě)的全文索引引擎工具包,它可以方便的嵌入到各種應(yīng)用中實(shí)現(xiàn)針對(duì)應(yīng)用的全文索引/檢索功能。
Lucene的作者:Lucene的貢獻(xiàn)者Doug Cutting是一位資深全文索引/檢索專(zhuān)家,曾經(jīng)是V-Twin搜索引擎(Apple的Copland操作系統(tǒng)的成就之一)的主要開(kāi)發(fā)者,后在 Excite擔(dān)任高級(jí)系統(tǒng)架構(gòu)設(shè)計(jì)師,目前從事于一些INTERNET底層架構(gòu)的研究。他貢獻(xiàn)出的Lucene的目標(biāo)是為各種中小型應(yīng)用程序加入全文檢索 功能。
Lucene的發(fā)展歷程:早先發(fā)布在作者自己的www.lucene.com,后來(lái)發(fā)布在SourceForge,2001年年底成為APACHE基金會(huì) jakarta的一個(gè)子項(xiàng)目:http://jakarta.apache.org/lucene/
已經(jīng)有很多Java項(xiàng)目都使用了Lucene作為其后臺(tái)的全文索引引擎,比較著名的有:
Jive:WEB論壇系統(tǒng);
Eyebrows:郵件列表HTML歸檔/瀏覽/查詢(xún)系統(tǒng),本文的主要參考文檔“TheLucene search engine: Powerful, flexible, and free”作者就是EyeBrows系統(tǒng)的主要開(kāi)發(fā)者之一,而EyeBrows已經(jīng)成為目前APACHE項(xiàng)目的主要郵件列表歸檔系統(tǒng)。
Cocoon:基于XML的web發(fā)布框架,全文檢索部分使用了Lucene
Eclipse:基于Java的開(kāi)放開(kāi)發(fā)平臺(tái),幫助部分的全文索引使用了Lucene
對(duì)于中文用戶(hù)來(lái)說(shuō),最關(guān)心的問(wèn)題是其是否支持中文的全文檢索。但通過(guò)后面對(duì)于Lucene的結(jié)構(gòu)的介紹,你會(huì)了解到由于Lucene良好架構(gòu)設(shè)計(jì),對(duì)中文的支持只需對(duì)其語(yǔ)言詞法分析接口進(jìn)行擴(kuò)展就能實(shí)現(xiàn)對(duì)中文檢索的支持。
全文檢索的實(shí)現(xiàn)機(jī)制
Lucene的API接口設(shè)計(jì)的比較通用,輸入輸出結(jié)構(gòu)都很像數(shù)據(jù)庫(kù)的表==>記錄==>字段,所以很多傳統(tǒng)的應(yīng)用的文件、數(shù)據(jù)庫(kù)等都可以比 較方便的映射到Lucene的存儲(chǔ)結(jié)構(gòu)/接口中。總體上看:可以先把Lucene當(dāng)成一個(gè)支持全文索引的數(shù)據(jù)庫(kù)系統(tǒng)。
比較一下Lucene和數(shù)據(jù)庫(kù):
Lucene 數(shù)據(jù)庫(kù)
索引數(shù)據(jù)源:doc(field1,field2...) doc(field1,field2...) \ indexer / _____________ | Lucene Index| -------------- / searcher \ 結(jié)果輸出:Hits(doc(field1,field2) doc(field1...)) 索引數(shù)據(jù)源:record(field1,field2...) record(field1..) \ SQL: insert/ _____________ | DB Index | ------------- / SQL: select \結(jié)果輸出:results(record(field1,field2..) record(field1...))
Document:一個(gè)需要進(jìn)行索引的“單元”
一個(gè)Document由多個(gè)字段組成 Record:記錄,包含多個(gè)字段
Field:字段 Field:字段
Hits:查詢(xún)結(jié)果集,由匹配的Document組成 RecordSet:查詢(xún)結(jié)果集,由多個(gè)Record組成
全文檢索 ≠ like "%keyword%"
通常比較厚的書(shū)籍后面常常附關(guān)鍵詞索引表(比如:北京:12, 34頁(yè),上海:3,77頁(yè)……),它能夠幫助讀者比較快地找到相關(guān)內(nèi)容的頁(yè)碼。而數(shù)據(jù)庫(kù)索引能夠大大提高查詢(xún)的速度原理也是一樣,想像一下通過(guò)書(shū)后面的索 引查找的速度要比一頁(yè)一頁(yè)地翻內(nèi)容高多少倍……而索引之所以效率高,另外一個(gè)原因是它是排好序的。對(duì)于檢索系統(tǒng)來(lái)說(shuō)核心是一個(gè)排序問(wèn)題。
由于數(shù)據(jù)庫(kù)索引不是為全文索引設(shè)計(jì)的,因此,使用like "%keyword%"時(shí),數(shù)據(jù)庫(kù)索引是不起作用的,在使用like查詢(xún)時(shí),搜索過(guò)程又變成類(lèi)似于一頁(yè)頁(yè)翻書(shū)的遍歷過(guò)程了,所以對(duì)于含有模糊查詢(xún)的數(shù)據(jù)庫(kù) 服務(wù)來(lái)說(shuō),LIKE對(duì)性能的危害是極大的。如果是需要對(duì)多個(gè)關(guān)鍵詞進(jìn)行模糊匹配:like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。
所以建立一個(gè)高效檢索系統(tǒng)的關(guān)鍵是建立一個(gè)類(lèi)似于科技索引一樣的反向索引機(jī)制,將數(shù)據(jù)源(比如多篇文章)排序順序存儲(chǔ)的同時(shí),有另外一個(gè)排好序的關(guān)鍵詞列 表,用于存儲(chǔ)關(guān)鍵詞==>文章映射關(guān)系,利用這樣的映射關(guān)系索引:[關(guān)鍵詞==>出現(xiàn)關(guān)鍵詞的文章編號(hào),出現(xiàn)次數(shù)(甚至包括位置:起始偏移 量,結(jié)束偏移量),出現(xiàn)頻率],檢索過(guò)程就是把模糊查詢(xún)變成多個(gè)可以利用索引的精確查詢(xún)的邏輯組合的過(guò)程。從而大大提高了多關(guān)鍵詞查詢(xún)的效率,所以,全文 檢索問(wèn)題歸結(jié)到最后是一個(gè)排序問(wèn)題。
由此可以看出模糊查詢(xún)相對(duì)數(shù)據(jù)庫(kù)的精確查詢(xún)是一個(gè)非常不確定的問(wèn)題,這也是大部分?jǐn)?shù)據(jù)庫(kù)對(duì)全文檢索支持有限的原因。Lucene最核心的特征是通過(guò)特殊的 索引結(jié)構(gòu)實(shí)現(xiàn)了傳統(tǒng)數(shù)據(jù)庫(kù)不擅長(zhǎng)的全文索引機(jī)制,并提供了擴(kuò)展接口,以方便針對(duì)不同應(yīng)用的定制。
可以通過(guò)一下表格對(duì)比一下數(shù)據(jù)庫(kù)的模糊查詢(xún):
Lucene全文索引引擎 數(shù)據(jù)庫(kù)
索引 將數(shù)據(jù)源中的數(shù)據(jù)都通過(guò)全文索引一一建立反向索引 對(duì)于LIKE查詢(xún)來(lái)說(shuō),數(shù)據(jù)傳統(tǒng)的索引是根本用不上的。數(shù)據(jù)需要逐個(gè)便利記錄進(jìn)行GREP式的模糊匹配,比有索引的搜索速度要有多個(gè)數(shù)量級(jí)的下降。
匹配效果 通過(guò)詞元(term)進(jìn)行匹配,通過(guò)語(yǔ)言分析接口的實(shí)現(xiàn),可以實(shí)現(xiàn)對(duì)中文等非英語(yǔ)的支持。 使用:like "%net%" 會(huì)把netherlands也匹配出來(lái),
多個(gè)關(guān)鍵詞的模糊匹配:使用like "%com%net%":就不能匹配詞序顛倒的xxx.net..xxx.com
匹配度 有匹配度算法,將匹配程度(相似度)比較高的結(jié)果排在前面。 沒(méi)有匹配程度的控制:比如有記錄中net出現(xiàn)5詞和出現(xiàn)1次的,結(jié)果是一樣的。
結(jié)果輸出 通過(guò)特別的算法,將最匹配度最高的頭100條結(jié)果輸出,結(jié)果集是緩沖式的小批量讀取的。 返回所有的結(jié)果集,在匹配條目非常多的時(shí)候(比如上萬(wàn)條)需要大量的內(nèi)存存放這些臨時(shí)結(jié)果集。
可定制性 通過(guò)不同的語(yǔ)言分析接口實(shí)現(xiàn),可以方便的定制出符合應(yīng)用需要的索引規(guī)則(包括對(duì)中文的支持) 沒(méi)有接口或接口復(fù)雜,無(wú)法定制
結(jié)論 高負(fù)載的模糊查詢(xún)應(yīng)用,需要負(fù)責(zé)的模糊查詢(xún)的規(guī)則,索引的資料量比較大 使用率低,模糊匹配規(guī)則簡(jiǎn)單或者需要模糊查詢(xún)的資料量少
全文檢索和數(shù)據(jù)庫(kù)應(yīng)用最大的不同在于:讓最相關(guān)的頭100條結(jié)果滿(mǎn)足98%以上用戶(hù)的需求
Lucene的創(chuàng)新之處:
大部分的搜索(數(shù)據(jù)庫(kù))引擎都是用B樹(shù)結(jié)構(gòu)來(lái)維護(hù)索引,索引的更新會(huì)導(dǎo)致大量的IO操作,Lucene在實(shí)現(xiàn)中,對(duì)此稍微有所改進(jìn):不是維護(hù)一個(gè)索引文 件,而是在擴(kuò)展索引的時(shí)候不斷創(chuàng)建新的索引文件,然后定期的把這些新的小索引文件合并到原先的大索引中(針對(duì)不同的更新策略,批次的大小可以調(diào)整),這樣 在不影響檢索的效率的前提下,提高了索引的效率。
Lucene和其他一些全文檢索系統(tǒng)/應(yīng)用的比較:
Lucene 其他開(kāi)源全文檢索系統(tǒng)
增量索引和批量索引 可以進(jìn)行增量的索引(Append),可以對(duì)于大量數(shù)據(jù)進(jìn)行批量索引,并且接口設(shè)計(jì)用于優(yōu)化批量索引和小批量的增量索引。 很多系統(tǒng)只支持批量的索引,有時(shí)數(shù)據(jù)源有一點(diǎn)增加也需要重建索引。
數(shù)據(jù)源 Lucene沒(méi)有定義具體的數(shù)據(jù)源,而是一個(gè)文檔的結(jié)構(gòu),因此可以非常靈活的適應(yīng)各種應(yīng)用(只要前端有合適的轉(zhuǎn)換器把數(shù)據(jù)源轉(zhuǎn)換成相應(yīng)結(jié)構(gòu)), 很多系統(tǒng)只針對(duì)網(wǎng)頁(yè),缺乏其他格式文檔的靈活性。
索引內(nèi)容抓取 Lucene的文檔是由多個(gè)字段組成的,甚至可以控制那些字段需要進(jìn)行索引,那些字段不需要索引,近一步索引的字段也分為需要分詞和不需要分詞的類(lèi)型:
需要進(jìn)行分詞的索引,比如:標(biāo)題,文章內(nèi)容字段
不需要進(jìn)行分詞的索引,比如:作者/日期字段 缺乏通用性,往往將文檔整個(gè)索引了
語(yǔ)言分析 通過(guò)語(yǔ)言分析器的不同擴(kuò)展實(shí)現(xiàn):
可以過(guò)濾掉不需要的詞:an the of 等,
西文語(yǔ)法分析:將jumps jumped jumper都?xì)w結(jié)成jump進(jìn)行索引/檢索
非英文支持:對(duì)亞洲語(yǔ)言,阿拉伯語(yǔ)言的索引支持 缺乏通用接口實(shí)現(xiàn)
查詢(xún)分析 通過(guò)查詢(xún)分析接口的實(shí)現(xiàn),可以定制自己的查詢(xún)語(yǔ)法規(guī)則:
比如: 多個(gè)關(guān)鍵詞之間的 + - and or關(guān)系等
并發(fā)訪(fǎng)問(wèn) 能夠支持多用戶(hù)的使用
關(guān)于亞洲語(yǔ)言的的切分詞問(wèn)題(Word Segment)
對(duì)于中文來(lái)說(shuō),全文索引首先還要解決一個(gè)語(yǔ)言分析的問(wèn)題,對(duì)于英文來(lái)說(shuō),語(yǔ)句中單詞之間是天然通過(guò)空格分開(kāi)的,但亞洲語(yǔ)言的中日韓文語(yǔ)句中的字是一個(gè)字挨一個(gè),所有,首先要把語(yǔ)句中按“詞”進(jìn)行索引的話(huà),這個(gè)詞如何切分出來(lái)就是一個(gè)很大的問(wèn)題。
首先,肯定不能用單個(gè)字符作(si-gram)為索引單元,否則查“上海”時(shí),不能讓含有“海上”也匹配。
但一句話(huà):“北京天安門(mén)”,計(jì)算機(jī)如何按照中文的語(yǔ)言習(xí)慣進(jìn)行切分呢?
“北京 天安門(mén)” 還是“北 京 天安門(mén)”?讓計(jì)算機(jī)能夠按照語(yǔ)言習(xí)慣進(jìn)行切分,往往需要機(jī)器有一個(gè)比較豐富的詞庫(kù)才能夠比較準(zhǔn)確的識(shí)別出語(yǔ)句中的單詞。
另外一個(gè)解決的辦法是采用自動(dòng)切分算法:將單詞按照2元語(yǔ)法(bigram)方式切分出來(lái),比如:
"北京天安門(mén)" ==> "北京 京天 天安 安門(mén)"。
這樣,在查詢(xún)的時(shí)候,無(wú)論是查詢(xún)"北京" 還是查詢(xún)"天安門(mén)",將查詢(xún)?cè)~組按同樣的規(guī)則進(jìn)行切分:"北京","天安安門(mén)",多個(gè)關(guān)鍵詞之間按與"and"的關(guān)系組合,同樣能夠正確地映射到相應(yīng)的索 引中。這種方式對(duì)于其他亞洲語(yǔ)言:韓文,日文都是通用的。
基于自動(dòng)切分的最大優(yōu)點(diǎn)是沒(méi)有詞表維護(hù)成本,實(shí)現(xiàn)簡(jiǎn)單,缺點(diǎn)是索引效率低,但對(duì)于中小型應(yīng)用來(lái)說(shuō),基于2元語(yǔ)法的切分還是夠用的。基于2元切分后的索引一般大小和源文件差不多,而對(duì)于英文,索引文件一般只有原文件的30%-40%不同,
自動(dòng)切分 詞表切分
實(shí)現(xiàn) 實(shí)現(xiàn)非常簡(jiǎn)單 實(shí)現(xiàn)復(fù)雜
查詢(xún) 增加了查詢(xún)分析的復(fù)雜程度, 適于實(shí)現(xiàn)比較復(fù)雜的查詢(xún)語(yǔ)法規(guī)則
存儲(chǔ)效率 索引冗余大,索引幾乎和原文一樣大 索引效率高,為原文大小的30%左右
維護(hù)成本 無(wú)詞表維護(hù)成本 詞表維護(hù)成本非常高:中日韓等語(yǔ)言需要分別維護(hù)。
還需要包括詞頻統(tǒng)計(jì)等內(nèi)容
適用領(lǐng)域 嵌入式系統(tǒng):運(yùn)行環(huán)境資源有限
分布式系統(tǒng):無(wú)詞表同步問(wèn)題
多語(yǔ)言環(huán)境:無(wú)詞表維護(hù)成本 對(duì)查詢(xún)和存儲(chǔ)效率要求高的專(zhuān)業(yè)搜索引擎
目前比較大的搜索引擎的語(yǔ)言分析算法一般是基于以上2個(gè)機(jī)制的結(jié)合。關(guān)于中文的語(yǔ)言分析算法,大家可以在Google查關(guān)鍵詞"wordsegment search"能找到更多相關(guān)的資料。
安裝和使用
下載:http://jakarta.apache.org/lucene/
注意:Lucene中的一些比較復(fù)雜的詞法分析是用JavaCC生成的(JavaCC:JavaCompilerCompiler,純Java的詞法分析 生成器),所以如果從源代碼編譯或需要修改其中的QueryParser、定制自己的詞法分析器,還需要從 https://javacc.dev.java.net/下載javacc。
lucene的組成結(jié)構(gòu):對(duì)于外部應(yīng)用來(lái)說(shuō)索引模塊(index)和檢索模塊(search)是主要的外部應(yīng)用入口
org.apache.Lucene.search/ 搜索入口
org.apache.Lucene.index/ 索引入口
org.apache.Lucene.analysis/ 語(yǔ)言分析器
org.apache.Lucene.queryParser/ 查詢(xún)分析器
org.apache.Lucene.document/ 存儲(chǔ)結(jié)構(gòu)
org.apache.Lucene.store/ 底層IO/存儲(chǔ)結(jié)構(gòu)
org.apache.Lucene.util/ 一些公用的數(shù)據(jù)結(jié)構(gòu)
簡(jiǎn)單的例子演示一下Lucene的使用方法:
索引過(guò)程:從命令行讀取文件名(多個(gè)),將文件分路徑(path字段)和內(nèi)容(body字段)2個(gè)字段進(jìn)行存儲(chǔ),并對(duì)內(nèi)容進(jìn)行全文索引:索引的單位是 Document對(duì)象,每個(gè)Document對(duì)象包含多個(gè)字段Field對(duì)象,針對(duì)不同的字段屬性和數(shù)據(jù)輸出的需求,對(duì)字段還可以選擇不同的索引/存儲(chǔ)字 段規(guī)則,列表如下: 方法 切詞 索引 存儲(chǔ) 用途
Field.Text(String name, String value) Yes Yes Yes 切分詞索引并存儲(chǔ),比如:標(biāo)題,內(nèi)容字段
Field.Text(String name, Reader value) Yes Yes No 切分詞索引不存儲(chǔ),比如:META信息,
不用于返回顯示,但需要進(jìn)行檢索內(nèi)容
Field.Keyword(String name, String value) No Yes Yes 不切分索引并存儲(chǔ),比如:日期字段
Field.UnIndexed(String name, String value) No No Yes 不索引,只存儲(chǔ),比如:文件路徑
Field.UnStored(String name, String value) Yes Yes No 只全文索引,不存儲(chǔ)
public class IndexFiles { //使用方法:: IndexFiles [索引輸出目錄](méi) [索引的文件列表] ... public static void main(String[] args) throws Exception { String indexPath = args[0]; IndexWriter writer; //用指定的語(yǔ)言分析器構(gòu)造一個(gè)新的寫(xiě)索引器(第3個(gè)參數(shù)表示是否為追加索引) writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false); for (int i=1; i<args.length; i++) { System.out.println("Indexing file " + args); InputStream is = new FileInputStream(args); //構(gòu)造包含2個(gè)字段Field的Document對(duì)象 //一個(gè)是路徑path字段,不索引,只存儲(chǔ) //一個(gè)是內(nèi)容body字段,進(jìn)行全文索引,并存儲(chǔ) Document doc = new Document(); doc.add(Field.UnIndexed("path", args)); doc.add(Field.Text("body", (Reader) new InputStreamReader(is))); //將文檔寫(xiě)入索引 writer.addDocument(doc); is.close(); }; //關(guān)閉寫(xiě)索引器 writer.close(); }} 索引過(guò)程中可以看到:
語(yǔ)言分析器提供了抽象的接口,因此語(yǔ)言分析(Analyser)是可以定制的,雖然lucene缺省提供了2個(gè)比較通用的分析器 SimpleAnalyser和StandardAnalyser,這2個(gè)分析器缺省都不支持中文,所以要加入對(duì)中文語(yǔ)言的切分規(guī)則,需要修改這2個(gè)分析 器。
Lucene并沒(méi)有規(guī)定數(shù)據(jù)源的格式,而只提供了一個(gè)通用的結(jié)構(gòu)(Document對(duì)象)來(lái)接受索引的輸入,因此輸入的數(shù)據(jù)源可以是:數(shù)據(jù)庫(kù),WORD文 檔,PDF文檔,HTML文檔……只要能夠設(shè)計(jì)相應(yīng)的解析轉(zhuǎn)換器將數(shù)據(jù)源構(gòu)造成成Docuement對(duì)象即可進(jìn)行索引。
對(duì)于大批量的數(shù)據(jù)索引,還可以通過(guò)調(diào)整IndexerWrite的文件合并頻率屬性(mergeFactor)來(lái)提高批量索引的效率。
檢索過(guò)程和結(jié)果顯示:
搜索結(jié)果返回的是Hits對(duì)象,可以通過(guò)它再訪(fǎng)問(wèn)Document==>Field中的內(nèi)容。
假設(shè)根據(jù)body字段進(jìn)行全文檢索,可以將查詢(xún)結(jié)果的path字段和相應(yīng)查詢(xún)的匹配度(score)打印出來(lái),
public class Search { public static void main(String[] args) throws Exception { String indexPath = args[0], queryString = args[1]; //指向索引目錄的搜索器 Searcher searcher = new IndexSearcher(indexPath); //查詢(xún)解析器:使用和索引同樣的語(yǔ)言分析器 Query query = QueryParser.parse(queryString, "body", new SimpleAnalyzer()); //搜索結(jié)果使用Hits存儲(chǔ) Hits hits = searcher.search(query); //通過(guò)hits可以訪(fǎng)問(wèn)到相應(yīng)字段的數(shù)據(jù)和查詢(xún)的匹配度 for (int i=0; i<hits.length(); i++) { System.out.println(hits.doc(i).get("path") + "; Score: " + hits.score(i)); }; }}在整個(gè)檢索過(guò)程中,語(yǔ)言分析器,查詢(xún)分析器,甚至搜索器(Searcher)都是提供了抽象的接口,可以根據(jù)需要進(jìn)行定制。
Hacking Lucene
簡(jiǎn)化的查詢(xún)分析器
個(gè)人感覺(jué)lucene成為JAKARTA項(xiàng)目后,畫(huà)在了太多的時(shí)間用于調(diào)試日趨復(fù)雜QueryParser,而其中大部分是大多數(shù)用戶(hù)并不很熟悉的,目前LUCENE支持的語(yǔ)法:
Query ::= ( Clause )*
Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")
中間的邏輯包括:and or + - &&||等符號(hào),而且還有"短語(yǔ)查詢(xún)"和針對(duì)西文的前綴/模糊查詢(xún)等,個(gè)人感覺(jué)對(duì)于一般應(yīng)用來(lái)說(shuō),這些功能有一些華而不實(shí),其實(shí)能夠?qū)崿F(xiàn) 目前類(lèi)似于Google的查詢(xún)語(yǔ)句分析功能其實(shí)對(duì)于大多數(shù)用戶(hù)來(lái)說(shuō)已經(jīng)夠了。所以,Lucene早期版本的QueryParser仍是比較好的選擇。
添加修改刪除指定記錄(Document)
Lucene提供了索引的擴(kuò)展機(jī)制,因此索引的動(dòng)態(tài)擴(kuò)展應(yīng)該是沒(méi)有問(wèn)題的,而指定記錄的修改也似乎只能通過(guò)記錄的刪除,然后重新加入實(shí)現(xiàn)。如何刪除指定的 記錄呢?刪除的方法也很簡(jiǎn)單,只是需要在索引時(shí)根據(jù)數(shù)據(jù)源中的記錄ID專(zhuān)門(mén)另建索引,然后利用IndexReader.delete(Termterm) 方法通過(guò)這個(gè)記錄ID刪除相應(yīng)的Document。
根據(jù)某個(gè)字段值的排序功能
lucene缺省是按照自己的相關(guān)度算法(score)進(jìn)行結(jié)果排序的,但能夠根據(jù)其他字段進(jìn)行結(jié)果排序是一個(gè)在LUCENE的開(kāi)發(fā)郵件列表中經(jīng)常提到的 問(wèn)題,很多原先基于數(shù)據(jù)庫(kù)應(yīng)用都需要除了基于匹配度(score)以外的排序功能。而從全文檢索的原理我們可以了解到,任何不基于索引的搜索過(guò)程效率都會(huì) 導(dǎo)致效率非常的低,如果基于其他字段的排序需要在搜索過(guò)程中訪(fǎng)問(wèn)存儲(chǔ)字段,速度回大大降低,因此非常是不可取的。
但這里也有一個(gè)折中的解決方法:在搜索過(guò)程中能夠影響排序結(jié)果的只有索引中已經(jīng)存儲(chǔ)的docID和score這2個(gè)參數(shù),所以,基于score以外的排 序,其實(shí)可以通過(guò)將數(shù)據(jù)源預(yù)先排好序,然后根據(jù)docID進(jìn)行排序來(lái)實(shí)現(xiàn)。這樣就避免了在LUCENE搜索結(jié)果外對(duì)結(jié)果再次進(jìn)行排序和在搜索過(guò)程中訪(fǎng)問(wèn)不 在索引中的某個(gè)字段值。
這里需要修改的是IndexSearcher中的HitCollector過(guò)程:
... scorer.score(new HitCollector() { private float minScore = 0.0f; public final void collect(int doc, float score) { if (score > 0.0f && // ignore zeroed buckets (bits==null || bits.get(doc))) { // skip docs not in bits totalHits[0]++; if (score >= minScore) { /* 原先:Lucene將docID和相應(yīng)的匹配度score例入結(jié)果命中列表中: * hq.put(new ScoreDoc(doc, score)); // update hit queue * 如果用doc 或 1/doc 代替 score,就實(shí)現(xiàn)了根據(jù)docID順排或逆排 * 假設(shè)數(shù)據(jù)源索引時(shí)已經(jīng)按照某個(gè)字段排好了序,而結(jié)果根據(jù)docID排序也就實(shí)現(xiàn)了 * 針對(duì)某個(gè)字段的排序,甚至可以實(shí)現(xiàn)更復(fù)雜的score和docID的擬合。 */ hq.put(new ScoreDoc(doc, (float) 1/doc )); if (hq.size() > nDocs) { // if hit queue overfull hq.pop(); // remove lowest in hit queue minScore = ((ScoreDoc)hq.top()).score; // reset minScore } } } } }, reader.maxDoc());
3, 計(jì)算匹配得分. 小于1.0的我們認(rèn)為是相關(guān)的記錄了.下面的代碼在輸出結(jié)果循環(huán)中. 如果要獲取完全匹配的記錄,
//計(jì)算匹配得分
Explanation explanation = searcher.explain(query, hits.id(i)) ;
System.out.println("匹配得分:"+explanation.getValue());
System.out.println("=========");
4, 關(guān)于短語(yǔ)匹配的用法
通過(guò)短語(yǔ)搜索:PhraseQuery
索引中包含了各個(gè)項(xiàng)的位置信息。PhraseQuery利用這些信息去搜索文檔,在這些文檔集中,我們所查找的各個(gè)項(xiàng)之間可能都相隔著一定的距離。例如, 假設(shè)某個(gè)域中包含了“the quick brown fox jumped over the lazy dog”這個(gè)短語(yǔ),即使我們不知道這個(gè)短語(yǔ)的確切完整寫(xiě)法,也一樣可以通過(guò)查找域中quick和fox距離相近的文檔來(lái)找出我們需要的文檔。當(dāng)然,一個(gè)簡(jiǎn) 單的TermQuery也能夠通過(guò)對(duì)這兩個(gè)項(xiàng)的單獨(dú)查詢(xún)成功地找到同樣文檔;但是在以上所討論的情況中,我們僅僅希望查到域中quick的位置緊挨著 fox或者隔一個(gè)不相關(guān)的單詞的文檔(如quick [不相關(guān)的詞] fox)。
在匹配的情況下,兩個(gè)項(xiàng)的位置之間允許的最大間隔距離稱(chēng)為slop。距離是指項(xiàng)要按順序組成給定的短語(yǔ),所需要移動(dòng)位置的次數(shù)。我們用剛剛提到的那個(gè)短 語(yǔ),看看slop因子是怎么樣工作的。首先,我們需要構(gòu)建一個(gè)小的基本測(cè)試構(gòu)架,程序里用一個(gè)setUp()方法來(lái)索引一個(gè)文檔,并通過(guò) matched(String[], int)方法構(gòu)造、執(zhí)行并斷言一個(gè)短語(yǔ)查詢(xún)與我們的測(cè)試文檔相匹配:
// 建立樣本文檔
由于只想示范一下幾個(gè)短語(yǔ)查詢(xún)的例子,因此在以上程序中我們簡(jiǎn)化了matched方法的代碼。這個(gè)程序按照一定的順序添加各個(gè)項(xiàng)來(lái)進(jìn)行短語(yǔ)查詢(xún)。默認(rèn)情況 下,PhraseQuery的slop因子設(shè)置為0,即要求查詢(xún)的結(jié)果必須和我們輸入的字符串組完全精確一致地匹配。通過(guò)setUp()和 matched()方法,測(cè)試用例對(duì)PhraseQuery的工作方式做出了簡(jiǎn)潔的示范。程序以查詢(xún)失敗或超出slop因子作為其邊界:
圖3.2 解釋短語(yǔ)查詢(xún)slop因子:短語(yǔ)“quick fox”需要slop值為1的移動(dòng)才能和原文檔匹配,而“fox quick”需要slop值為3的移動(dòng)才能匹配
在短語(yǔ)查詢(xún)中,雖然項(xiàng)出現(xiàn)的先后順序會(huì)對(duì)slop因子的選取有一定影響,但是我們不一定需要按照這些項(xiàng)在文檔中出現(xiàn)的先后順序來(lái)將它們添加至 PhraseQuery中。例如,如果把上述String數(shù)組中的兩個(gè)項(xiàng)顛倒(先是項(xiàng)“fox”,然后是“quick”),要和文檔匹配就需要移動(dòng)三個(gè)位 置,而不是原先的一個(gè)了。為了表達(dá)得更形象一些,可以思考一下單詞“fox”需要移動(dòng)多少個(gè)位置才能位于單詞“quick”的兩個(gè)位置之后。你會(huì)發(fā)現(xiàn) fox移動(dòng)一次到達(dá)quick的位置,然后再移動(dòng)兩次才能使之變成“quick X fox”,從而和“quick brown fox”充分地匹配。
圖3.2展示了slop位置因子在這兩個(gè)短語(yǔ)查詢(xún)場(chǎng)景的應(yīng)用是如何工作的,下面的測(cè)試用例示范了程序如何通過(guò)slop因子的設(shè)置實(shí)現(xiàn)對(duì)String[] {"fox", "quick"}的匹配:
現(xiàn)在我們開(kāi)始深入學(xué)習(xí)如何對(duì)多個(gè)項(xiàng)進(jìn)行復(fù)合查詢(xún)的問(wèn)題。
復(fù)合項(xiàng)短語(yǔ)
PhraseQuery支持復(fù)合項(xiàng)短語(yǔ)(multiple-term phrases)。不管短語(yǔ)中有多少個(gè)項(xiàng),slop因子都規(guī)定了項(xiàng)按順序移動(dòng)位置的所允許的最大值。下面看看關(guān)于復(fù)合項(xiàng)短語(yǔ)查詢(xún)的一個(gè)示例:
到目前為止,你已經(jīng)了解了短語(yǔ)查詢(xún)是如何進(jìn)行匹配的,下面我們把注意力轉(zhuǎn)向于短語(yǔ)查詢(xún)對(duì)文檔評(píng)分的影響。
短語(yǔ)查詢(xún)?cè)u(píng)分
短語(yǔ)查詢(xún)是根據(jù)短語(yǔ)匹配所需要的編輯距離來(lái)進(jìn)行評(píng)分的。項(xiàng)之間距離越小的匹配具有的權(quán)重也就越大。短語(yǔ)查詢(xún)的評(píng)分因子如圖3.3所示。評(píng)分與距離成反比關(guān)系,距離越大的匹配其評(píng)分越低。
圖3.3 短語(yǔ)查詢(xún)的評(píng)分公式
注:在QueryParser的分析表達(dá)式中雙引號(hào)里的若干個(gè)項(xiàng)被被轉(zhuǎn)換為一個(gè)PhraseQuery對(duì)象。Slop因子的默認(rèn)值是0,但是你可以在 QueryParser的查詢(xún)表達(dá)式中加上~n的聲明,以此來(lái)調(diào)整slop因子的值。例如,表達(dá)式“quick fox”~3的意義為:為fox和quick項(xiàng)生成一個(gè)slop因子為3的PhraseQuery對(duì)象。更多關(guān)于PhraseQuery和slop因子的 細(xì)節(jié)請(qǐng)參看3.5.6小節(jié)。短語(yǔ)由傳給QueryParser的分析器進(jìn)行分析,在此過(guò)程中還會(huì)加入另外一個(gè)復(fù)雜的層,這個(gè)內(nèi)容將會(huì)在4.1.2小節(jié)中加 以討論。
ExtJS教程- Hibernate教程-Struts2 教程-Lucene教程