Lucene不是一個完整的全文索引應用,而是是一個用Java寫的全文索引引擎工具包,它能夠方便的嵌入到各類應用中實現針對應用的全文索引/檢索功能。前端
Lucene的做者:Lucene的貢獻者Doug Cutting是一位資深全文索引/檢索專家,曾經是V-Twin搜索引擎(Apple的Copland操做系統的成就之一)的主要開發者,後在Excite擔任高級系統架構設計師,目前從事於一些INTERNET底層架構的研究。他貢獻出的Lucene的目標是爲各類中小型應用程序加入全文檢索功能。java
Lucene的發展歷程:早先發布在做者本身的www.lucene.com,後來發佈在SourceForge,2001年年末成爲APACHE基金會jakarta的一個子項目:http://jakarta.apache.org/lucene/web
已經有不少Java項目都使用了Lucene做爲其後臺的全文索引引擎,比較著名的有:算法
Eclipse:基於Java的開放開發平臺,幫助部分的全文索引使用了Lucene數據庫
對於中文用戶來講,最關心的問題是其是否支持中文的全文檢索。但經過後面對於Lucene的結構的介紹,你會了解到因爲Lucene良好架構設計,對中文的支持只需對其語言詞法分析接口進行擴展就能實現對中文檢索的支持。apache
Lucene的API接口設計的比較通用,輸入輸出結構都很像數據庫的表==>記錄==>字段,因此不少傳統的應用的文件、數據庫等均可以比較方便的映射到Lucene的存儲結構/接口中。整體上看:能夠先把Lucene當成一個支持全文索引的數據庫系統。數據結構
比較一下Lucene和數據庫:架構
Lucene | 數據庫 |
索引數據源:doc(field1,field2...) doc(field1,field2...) \ indexer / _____________ | Lucene Index| -------------- / searcher \ 結果輸出:Hits(doc(field1,field2) doc(field1...)) |
索引數據源:record(field1,field2...) record(field1..) \ SQL: insert/ _____________ | DB Index | ------------- / SQL: select \ 結果輸出:results(record(field1,field2..) record(field1...)) |
Document:一個須要進行索引的「單元」 一個Document由多個字段組成 |
Record:記錄,包含多個字段 |
Field:字段 | Field:字段 |
Hits:查詢結果集,由匹配的Document組成 | RecordSet:查詢結果集,由多個Record組成 |
全文檢索 ≠ like "%keyword%"
一般比較厚的書籍後面經常附關鍵詞索引表(好比:北京:12, 34頁, 上海:3,77頁……),它可以幫助讀者比較快地找到相關內容的頁碼。而數據庫索引可以大大提升查詢的速度原理也是同樣,想像一下經過書後面的索引查找的速度要比一頁一頁地翻內容高多少倍……而索引之因此效率高,另一個緣由是它是排好序的。對於檢索系統來講核心是一個排序問題。
因爲數據庫索引不是爲全文索引設計的,所以,使用like "%keyword%"時,數據庫索引是不起做用的,在使用like查詢時,搜索過程又變成相似於一頁頁翻書的遍歷過程了,因此對於含有模糊查詢的數據庫服務來講,LIKE對性能的危害是極大的。若是是須要對多個關鍵詞進行模糊匹配:like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。
因此創建一個高效檢索系統的關鍵是創建一個相似於科技索引同樣的反向索引機制,將數據源(好比多篇文章)排序順序存儲的同時,有另一個排好序的關鍵詞列表,用於存儲關鍵詞==>文章映射關係,利用這樣的映射關係索引:[關鍵詞==>出現關鍵詞的文章編號,出現次數(甚至包括位置:起始偏移量,結束偏移量),出現頻率],檢索過程就是把模糊查詢變成多個能夠利用索引的精確查詢的邏輯組合的過程。從而大大提升了多關鍵詞查詢的效率,因此,全文檢索問題歸結到最後是一個排序問題。
由此能夠看出模糊查詢相對數據庫的精確查詢是一個很是不肯定的問題,這也是大部分數據庫對全文檢索支持有限的緣由。Lucene最核心的特徵是經過特殊的索引結構實現了傳統數據庫不擅長的全文索引機制,並提供了擴展接口,以方便針對不一樣應用的定製。
能夠經過一下表格對比一下數據庫的模糊查詢:
Lucene全文索引引擎 | 數據庫 | |
索引 | 將數據源中的數據都經過全文索引一一創建反向索引 | 對於LIKE查詢來講,數據傳統的索引是根本用不上的。數據須要逐個便利記錄進行GREP式的模糊匹配,比有索引的搜索速度要有多個數量級的降低。 |
匹配效果 | 經過詞元(term)進行匹配,經過語言分析接口的實現,能夠實現對中文等非英語的支持。 | 使用:like "%net%" 會把netherlands也匹配出來, 多個關鍵詞的模糊匹配:使用like "%com%net%":就不能匹配詞序顛倒的xxx.net..xxx.com |
匹配度 | 有匹配度算法,將匹配程度(類似度)比較高的結果排在前面。 | 沒有匹配程度的控制:好比有記錄中net出現5詞和出現1次的,結果是同樣的。 |
結果輸出 | 經過特別的算法,將最匹配度最高的頭100條結果輸出,結果集是緩衝式的小批量讀取的。 | 返回全部的結果集,在匹配條目很是多的時候(好比上萬條)須要大量的內存存放這些臨時結果集。 |
可定製性 | 經過不一樣的語言分析接口實現,能夠方便的定製出符合應用須要的索引規則(包括對中文的支持) | 沒有接口或接口複雜,沒法定製 |
結論 | 高負載的模糊查詢應用,須要負責的模糊查詢的規則,索引的資料量比較大 | 使用率低,模糊匹配規則簡單或者須要模糊查詢的資料量少 |
全文檢索和數據庫應用最大的不一樣在於:讓最相關的頭100條結果知足98%以上用戶的需求
Lucene的創新之處:
大部分的搜索(數據庫)引擎都是用B樹結構來維護索引,索引的更新會致使大量的IO操做,Lucene在實現中,對此稍微有所改進:不是維護一個索引文件,而是在擴展索引的時候不斷建立新的索引文件,而後按期的把這些新的小索引文件合併到原先的大索引中(針對不一樣的更新策略,批次的大小能夠調整),這樣在不影響檢索的效率的前提下,提升了索引的效率。
Lucene和其餘一些全文檢索系統/應用的比較:
Lucene | 其餘開源全文檢索系統 | |
增量索引和批量索引 | 能夠進行增量的索引(Append),能夠對於大量數據進行批量索引,而且接口設計用於優化批量索引和小批量的增量索引。 | 不少系統只支持批量的索引,有時數據源有一點增長也須要重建索引。 |
數據源 | Lucene沒有定義具體的數據源,而是一個文檔的結構,所以能夠很是靈活的適應各類應用(只要前端有合適的轉換器把數據源轉換成相應結構), | 不少系統只針對網頁,缺少其餘格式文檔的靈活性。 |
索引內容抓取 | Lucene的文檔是由多個字段組成的,甚至能夠控制那些字段須要進行索引,那些字段不須要索引,近一步索引的字段也分爲須要分詞和不須要分詞的類型: 須要進行分詞的索引,好比:標題,文章內容字段 不須要進行分詞的索引,好比:做者/日期字段 |
缺少通用性,每每將文檔整個索引了 |
語言分析 | 經過語言分析器的不一樣擴展實現: 能夠過濾掉不須要的詞:an the of 等, 西文語法分析:將jumps jumped jumper都歸結成jump進行索引/檢索 非英文支持:對亞洲語言,阿拉伯語言的索引支持 |
缺少通用接口實現 |
查詢分析 | 經過查詢分析接口的實現,能夠定製本身的查詢語法規則: 好比: 多個關鍵詞之間的 + - and or關係等 |
|
併發訪問 | 可以支持多用戶的使用 |
對於中文來講,全文索引首先還要解決一個語言分析的問題,對於英文來講,語句中單詞之間是自然經過空格分開的,但亞洲語言的中日韓文語句中的字是一個字挨一個,全部,首先要把語句中按「詞」進行索引的話,這個詞如何切分出來就是一個很大的問題。
首先,確定不能用單個字符做(si-gram)爲索引單元,不然查「上海」時,不能讓含有「海上」也匹配。
但一句話:「北京天安門」,計算機如何按照中文的語言習慣進行切分呢?
「北京 天安門」 仍是「北 京 天安門」?讓計算機可以按照語言習慣進行切分,每每須要機器有一個比較豐富的詞庫纔可以比較準確的識別出語句中的單詞。
另一個解決的辦法是採用自動切分算法:將單詞按照2元語法(bigram)方式切分出來,好比:
"北京天安門" ==> "北京 京天 天安 安門"。
這樣,在查詢的時候,不管是查詢"北京" 仍是查詢"天安門",將查詢詞組按一樣的規則進行切分:"北京","天安安門",多個關鍵詞之間按與"and"的關係組合,一樣可以正確地映射到相應的索引中。這種方式對於其餘亞洲語言:韓文,日文都是通用的。
基於自動切分的最大優勢是沒有詞表維護成本,實現簡單,缺點是索引效率低,但對於中小型應用來講,基於2元語法的切分仍是夠用的。基於2元切分後的索引通常大小和源文件差很少,而對於英文,索引文件通常只有原文件的30%-40%不一樣,
自動切分 | 詞表切分 | |
實現 | 實現很是簡單 | 實現複雜 |
查詢 | 增長了查詢分析的複雜程度, | 適於實現比較複雜的查詢語法規則 |
存儲效率 | 索引冗餘大,索引幾乎和原文同樣大 | 索引效率高,爲原文大小的30%左右 |
維護成本 | 無詞表維護成本 | 詞表維護成本很是高:中日韓等語言須要分別維護。 還須要包括詞頻統計等內容 |
適用領域 | 嵌入式系統:運行環境資源有限 分佈式系統:無詞表同步問題 多語言環境:無詞表維護成本 |
對查詢和存儲效率要求高的專業搜索引擎 |
目前比較大的搜索引擎的語言分析算法通常是基於以上2個機制的結合。關於中文的語言分析算法,你們能夠在Google查關鍵詞"wordsegment search"能找到更多相關的資料。
下載:http://jakarta.apache.org/lucene/
注意:Lucene中的一些比較複雜的詞法分析是用JavaCC生成的(JavaCC:JavaCompilerCompiler,純Java的詞法分析生成器),因此若是從源代碼編譯或須要修改其中的QueryParser、定製本身的詞法分析器,還須要從https://javacc.dev.java.net/下載javacc。
lucene的組成結構:對於外部應用來講索引模塊(index)和檢索模塊(search)是主要的外部應用入口
org.apache.Lucene.search/ | 搜索入口 |
org.apache.Lucene.index/ | 索引入口 |
org.apache.Lucene.analysis/ | 語言分析器 |
org.apache.Lucene.queryParser/ | 查詢分析器 |
org.apache.Lucene.document/ | 存儲結構 |
org.apache.Lucene.store/ | 底層IO/存儲結構 |
org.apache.Lucene.util/ | 一些公用的數據結構 |
簡單的例子演示一下Lucene的使用方法:
索引過程:從命令行讀取文件名(多個),將文件分路徑(path字段)和內容(body字段)2個字段進行存儲,並對內容進行全文索引:索引的單位是Document對象,每一個Document對象包含多個字段Field對象,針對不一樣的字段屬性和數據輸出的需求,對字段還能夠選擇不一樣的索引/存儲字段規則,列表以下:方法 | 切詞 | 索引 | 存儲 | 用途 |
---|---|---|---|---|
Field.Text(String name, String value) | Yes | Yes | Yes | 切分詞索引並存儲,好比:標題,內容字段 |
Field.Text(String name, Reader value) | Yes | Yes | No | 切分詞索引不存儲,好比:META信息, 不用於返回顯示,但須要進行檢索內容 |
Field.Keyword(String name, String value) | No | Yes | Yes | 不切分索引並存儲,好比:日期字段 |
Field.UnIndexed(String name, String value) | No | No | Yes | 不索引,只存儲,好比:文件路徑 |
Field.UnStored(String name, String value) | Yes | Yes | No | 只全文索引,不存儲 |
public class IndexFiles { //使用方法:: IndexFiles [索引輸出目錄] [索引的文件列表] ... public static void main(String[] args) throws Exception { String indexPath = args[0]; IndexWriter writer; //用指定的語言分析器構造一個新的寫索引器(第3個參數表示是否爲追加索引) writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false); for (int i=1; i<args.length; i++) { System.out.println("Indexing file " + args[i]); InputStream is = new FileInputStream(args[i]); //構造包含2個字段Field的Document對象 //一個是路徑path字段,不索引,只存儲 //一個是內容body字段,進行全文索引,並存儲 Document doc = new Document(); doc.add(Field.UnIndexed("path", args[i])); doc.add(Field.Text("body", (Reader) new InputStreamReader(is))); //將文檔寫入索引 writer.addDocument(doc); is.close(); }; //關閉寫索引器 writer.close(); } }
索引過程當中能夠看到:
檢索過程和結果顯示:
搜索結果返回的是Hits對象,能夠經過它再訪問Document==>Field中的內容。
假設根據body字段進行全文檢索,能夠將查詢結果的path字段和相應查詢的匹配度(score)打印出來,
public class Search { public static void main(String[] args) throws Exception { String indexPath = args[0], queryString = args[1]; //指向索引目錄的搜索器 Searcher searcher = new IndexSearcher(indexPath); //查詢解析器:使用和索引一樣的語言分析器 Query query = QueryParser.parse(queryString, "body", new SimpleAnalyzer()); //搜索結果使用Hits存儲 Hits hits = searcher.search(query); //經過hits能夠訪問到相應字段的數據和查詢的匹配度 for (int i=0; i<hits.length(); i++) { System.out.println(hits.doc(i).get("path") + "; Score: " + hits.score(i)); }; } }在整個檢索過程當中,語言分析器,查詢分析器,甚至搜索器(Searcher)都是提供了抽象的接口,能夠根據須要進行定製。
簡化的查詢分析器
我的感受lucene成爲JAKARTA項目後,畫在了太多的時間用於調試日趨複雜QueryParser,而其中大部分是大多數用戶並不很熟悉的,目前LUCENE支持的語法:
Query ::= ( Clause )*
Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")")
中間的邏輯包括:and or + - &&||等符號,並且還有"短語查詢"和針對西文的前綴/模糊查詢等,我的感受對於通常應用來講,這些功能有一些華而不實,其實可以實現目前相似於Google的查詢語句分析功能其實對於大多數用戶來講已經夠了。因此,Lucene早期版本的QueryParser還是比較好的選擇。
添加修改刪除指定記錄(Document)
Lucene提供了索引的擴展機制,所以索引的動態擴展應該是沒有問題的,而指定記錄的修改也彷佛只能經過記錄的刪除,而後從新加入實現。如何刪除指定的記錄呢?刪除的方法也很簡單,只是須要在索引時根據數據源中的記錄ID專門另建索引,而後利用IndexReader.delete(Termterm)方法經過這個記錄ID刪除相應的Document。
根據某個字段值的排序功能
lucene缺省是按照本身的相關度算法(score)進行結果排序的,但可以根據其餘字段進行結果排序是一個在LUCENE的開發郵件列表中常常提到的問題,不少原先基於數據庫應用都須要除了基於匹配度(score)之外的排序功能。而從全文檢索的原理咱們能夠了解到,任何不基於索引的搜索過程效率都會致使效率很是的低,若是基於其餘字段的排序須要在搜索過程當中訪問存儲字段,速度回大大下降,所以很是是不可取的。
但這裏也有一個折中的解決方法:在搜索過程當中可以影響排序結果的只有索引中已經存儲的docID和score這2個參數,因此,基於score之外的排序,其實能夠經過將數據源預先排好序,而後根據docID進行排序來實現。這樣就避免了在LUCENE搜索結果外對結果再次進行排序和在搜索過程當中訪問不在索引中的某個字段值。
這裏須要修改的是IndexSearcher中的HitCollector過程:
... 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和相應的匹配度score例入結果命中列表中: * hq.put(new ScoreDoc(doc, score)); // update hit queue * 若是用doc 或 1/doc 代替 score,就實現了根據docID順排或逆排 * 假設數據源索引時已經按照某個字段排好了序,而結果根據docID排序也就實現了 * 針對某個字段的排序,甚至能夠實現更復雜的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());
更通用的輸入輸出接口
雖然lucene沒有定義一個肯定的輸入文檔格式,但愈來愈多的人想到使用一個標準的中間格式做爲Lucene的數據導入接口,而後其餘數據,好比PDF只須要經過解析器轉換成標準的中間格式就能夠進行數據索引了。這個中間格式主要以XML爲主,相似實現已經不下4,5個:
數據源: WORD PDF HTML DB other \ | | | / XML中間格式 | Lucene INDEX
目前尚未針對MSWord文檔的解析器,由於Word文檔和基於ASCII的RTF文檔不一樣,須要使用COM對象機制解析。這個是我在Google上查的相關資料:http://www.intrinsyc.com/products/enterprise_applications.asp
另一個辦法就是把Word文檔轉換成text:http://www.winfield.demon.nl/index.html
索引過程優化
索引通常分2種狀況,一種是小批量的索引擴展,一種是大批量的索引重建。在索引過程當中,並非每次新的DOC加入進去索引都從新進行一次索引文件的寫入操做(文件I/O是一件很是消耗資源的事情)。
Lucene先在內存中進行索引操做,並根據必定的批量進行文件的寫入。這個批次的間隔越大,文件的寫入次數越少,但佔用內存會不少。反之佔用內存少,但文件IO操做頻繁,索引速度會很慢。在IndexWriter中有一個MERGE_FACTOR參數能夠幫助你在構造索引器後根據應用環境的狀況充分利用內存減小文件的操做。根據個人使用經驗:缺省Indexer是每20條記錄索引後寫入一次,每將MERGE_FACTOR增長50倍,索引速度能夠提升1倍左右。
搜索過程優化
lucene支持內存索引:這樣的搜索比基於文件的I/O有數量級的速度提高。
http://www.onjava.com/lpt/a/3273
而儘量減小IndexSearcher的建立和對搜索結果的前臺的緩存也是必要的。
Lucene面向全文檢索的優化在於首次索引檢索後,並不把全部的記錄(Document)具體內容讀取出來,而起只將全部結果中匹配度最高的頭100條結果(TopDocs)的ID放到結果集緩存中並返回,這裏能夠比較一下數據庫檢索:若是是一個10,000條的數據庫檢索結果集,數據庫是必定要把全部記錄內容都取得之後再開始返回給應用結果集的。因此即便檢索匹配總數不少,Lucene的結果集佔用的內存空間也不會不少。對於通常的模糊檢索應用是用不到這麼多的結果的,頭100條已經能夠知足90%以上的檢索需求。
若是首批緩存結果數用完後還要讀取更後面的結果時Searcher會再次檢索並生成一個上次的搜索緩存數大1倍的緩存,並再從新向後抓取。因此若是構造一個Searcher去查1-120條結果,Searcher實際上是進行了2次搜索過程:頭100條取完後,緩存結果用完,Searcher從新檢索再構造一個200條的結果緩存,依此類推,400條緩存,800條緩存。因爲每次Searcher對象消失後,這些緩存也訪問那不到了,你有可能想將結果記錄緩存下來,緩存數儘可能保證在100如下以充分利用首次的結果緩存,不讓Lucene浪費屢次檢索,並且能夠分級進行結果緩存。
Lucene的另一個特色是在收集結果的過程當中將匹配度低的結果自動過濾掉了。這也是和數據庫應用須要將搜索的結果所有返回不一樣之處。
Luene的確是一個面對對象設計的典範
這些優勢都是很是值得在之後的開發中學習借鑑的。做爲一個通用工具包,Lunece的確給予了須要將全文檢索功能嵌入到應用中的開發者不少的便利。
此外,經過對Lucene的學習和使用,我也更深入地理解了爲何不少數據庫優化設計中要求,好比:
參考資料:
Apache: Lucene Project
http://jakarta.apache.org/lucene/
Lucene開發/用戶郵件列表歸檔
Lucene-dev@jakarta.apache.org
Lucene-user@jakarta.apache.org
The Lucene search engine: Powerful, flexible, and free
http://www.javaworld.com/javaworld/jw-09-2000/jw-0915-Lucene_p.html
Lucene Tutorial
http://www.darksleep.com/puff/lucene/lucene.html
Notes on distributed searching with Lucene
http://home.clara.net/markharwood/lucene/
中文語言的切分詞
http://www.google.com/search?sourceid=navclient&hl=zh-CN&q=chinese+word+segment
搜索引擎工具介紹
http://searchtools.com/
Lucene做者Cutting的幾篇論文和專利
http://lucene.sourceforge.net/publications.html
Lucene的.NET實現:dotLucene
http://sourceforge.net/projects/dotlucene/
Lucene做者Cutting的另一個項目:基於Java的搜索引擎Nutch
http://www.nutch.org/ http://sourceforge.net/projects/nutch/
關於基於詞表和N-Gram的切分詞比較
http://china.nikkeibp.co.jp/cgi-bin/china/news/int/int200302100112.html