原文:http://jalorsoft.com/holen/holen_lucene_01.html
本文是Lucene研究文集的首篇,主要介紹了Lucene的起源、發(fā)展、現(xiàn)狀,以及Luence的初步應用,可以作為了解和學習Lucene的入門資料。
1. 起源與發(fā)展
Lucene是一個高性能、純Java的全文檢索引擎,而且免費、開源。Lucene幾乎適合于任何需要全文檢索的應用,尤其是跨平臺的應用。
Lucene的作者Doug Cutting是一個資深的全文檢索專家,剛開始,Doug Cutting將Lucene發(fā)表在自己的主頁上,2000年3月將其轉移到sourceforge,于2001年10捐獻給Apache,作為Jakarta的一個子工程。
2.使用現(xiàn)狀
經(jīng)過多年的發(fā)展,Lucene在全文檢索領域已經(jīng)有了很多的成功案例,并積累了良好的聲譽。
基于Lucene的全文檢索產品(Lucene本身只是一個組件,而非一個完整的應用)和應用Lucene的項目在世界各地已經(jīng)非常之多,比較知名的有:
l Eclipse:主流Java開發(fā)工具,其幫助文檔采用Lucene作為檢索引擎
l Jive:知名論壇系統(tǒng),其檢索功能基于Lucene
l Ifinder:出自德國的網(wǎng)站檢索系統(tǒng),基于Lucene(http://ifinder.intrafind.org/)
l MIT DSpace Federation:一個文檔管理系統(tǒng)(http://www.dspace.org/)
國內外采用Lucene作為網(wǎng)站全文檢索引擎的也很多,比較知名的有:
l http://www.blogchina.com/weblucene/
l http://www.ioffer.com/
l http://search.soufun.com/
l http://www.taminn.com/
(更多案例,請參見http://wiki.apache.org/jakarta-lucene/PoweredBy)
在所有這些案例中,開源應用占了很大一部分,但更多的還是商化業(yè)產品和網(wǎng)站。毫不夸張的說,Lucene的出現(xiàn),極大的推動了全文檢索技術在各個行業(yè)或領域中的深層次應用。
3.初步應用
前面提到,Lucene本身只是一個組件,而非一個完整的應用,所以若想讓Lucene跑起來,還得在Lucene基礎上進行必要的二次開發(fā)。
下載與安裝
首先,你需要到Lucene的官方網(wǎng)站http://jakarta.apache.org/lucene/ 去下載一份拷貝,最新版是1.4。下載后將得到一個名為lucene-1.4-final.zip的壓縮文件,將其解壓,里面有一個名為lucene-1.4-final.jar的文件,這就是Lucene組件包了,若需要在項目使用Lucene,只需要把lucene-1.4-final.jar置于類路徑下即可,至于解壓后的其他文件都是參考用的。
接下來,我用Eclipse建立一個工程,實現(xiàn)基于Lucene的建庫、記錄加載和記錄查詢等功能。
如上圖所示,這是開發(fā)完成后的工程,其中有三個源文件CreateDataBase.java,InsertRecords.java,QueryRecords.java,分別實現(xiàn)建庫、入庫、檢索的功能。
以下是對這三個源文件的分析。
建庫源碼及說明
CreateDataBase.java |
package com.holen.part1; import java.io.File; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.index.IndexWriter; /** * @author Holen Chen * 初始化檢索庫 */ public class CreateDataBase { public CreateDataBase() { } public int createDataBase(File file){ int returnValue = 0; if(!file.isDirectory()){ file.mkdirs(); } try{ IndexWriter indexWriter = new IndexWriter(file,new StandardAnalyzer(),true); indexWriter.close(); returnValue = 1; }catch(Exception ex){ ex.printStackTrace(); } return returnValue; } /** * 傳入檢索庫路徑,初始化庫 * @param file * @return */ public int createDataBase(String file){ return this.createDataBase(new File(file)); } public static void main(String[] args) { CreateDataBase temp = new CreateDataBase(); if(temp.createDataBase("e:\\lucene\\holendb") == 1){ System.out.println("db init succ"); } } } |
說明:這里最關鍵的語句是IndexWriter indexWriter = new IndexWriter(file,new StandardAnalyzer(),true)。
第一個參數(shù)是庫的路徑,也就是說你準備把全文檢索庫保存在哪個位置,比如main方法中設定的“e:\\lucene\\holendb”,Lucene支持多庫,且每個庫的位置允許不同。
第二個參數(shù)是分析器,這里采用的是Lucene自帶的標準分析器,分析器用于對整篇文章進行分詞解析,這里的標準分析器實現(xiàn)對英文(或拉丁文,凡是由字母組成,由空格分開的文字均可)的分詞,分析器將把整篇英文按空格切成一個個的單詞(在全文檢索里這叫切詞,切詞是全文檢索的核心技術之一,Lucene默認只能切英文或其他拉丁文,默認不支持中日韓等雙字節(jié)文字,關于中文切詞技術將在后續(xù)章節(jié)重點探討)。
第三個參數(shù)是是否初始化庫,這里我設的是true,true意味著新建庫或覆蓋已經(jīng)存在的庫,false意味著追加到已經(jīng)存在的庫。這里新建庫,所以肯定需要初始化,初始化后,庫目錄下只存在一個名為segments的文件,大小為1k。但是當庫中存在記錄時執(zhí)行初始化,庫中內容將全部丟失,庫回復到初始狀態(tài),即相當于新建了該庫,所以真正做項目時,該方法一定要慎用。
加載記錄源碼及說明
InsertRecords.java |
package com.holen.part1; import java.io.File; import java.io.FileReader; import java.io.Reader; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; /** * @author Holen Chen * 記錄加載 */ public class InsertRecords { public InsertRecords() { } public int insertRecords(String dbpath,File file){ int returnValue = 0; try{ IndexWriter indexWriter = new IndexWriter(dbpath,new StandardAnalyzer(),false); this.addFiles(indexWriter,file); returnValue = 1; }catch(Exception ex){ ex.printStackTrace(); } return returnValue; } /** * 傳入需加載的文件名 * @param file * @return */ public int insertRecords(String dbpath,String file){ return this.insertRecords(dbpath,new File(file)); } public void addFiles(IndexWriter indexWriter,File file){ Document doc = new Document(); try{ doc.add(Field.Keyword("filename",file.getName())); //以下兩句只能取一句,前者是索引不存儲,后者是索引且存儲 //doc.add(Field.Text("content",new FileReader(file))); doc.add(Field.Text("content",this.chgFileToString(file))); indexWriter.addDocument(doc); indexWriter.close(); }catch(Exception ex){ ex.printStackTrace(); } } /** * 從文本文件中讀取內容 * @param file * @return */ public String chgFileToString(File file){ String returnValue = null; StringBuffer sb = new StringBuffer(); char[] c = new char[4096]; try{ Reader reader = new FileReader(file); int n = 0; while(true){ n = reader.read(c); if(n > 0){ sb.append(c,0,n); }else{ break; } } reader.close(); }catch(Exception ex){ ex.printStackTrace(); } returnValue = sb.toString(); return returnValue; } public static void main(String[] args) { InsertRecords temp = new InsertRecords(); String dbpath = "e:\\lucene\\holendb"; //holen1.txt中包含關鍵字"holen"和"java" if(temp.insertRecords(dbpath,"e:\\lucene\\holen1.txt") == 1){ System.out.println("add file1 succ"); } //holen2.txt中包含關鍵字"holen"和"chen" if(temp.insertRecords(dbpath,"e:\\lucene\\holen2.txt") == 1){ System.out.println("add file2 succ"); } } } |
說明:這個類里面主要有3個方法insertRecords(String dbpath,File file),addFiles(IndexWriter indexWriter,File file),chgFileToString(File file)。
ChgFileToString方法用于讀取文本型文件到一個String變量中。
InsertRecords方法用于加載一條記錄,這里是將單個文件入全文檢索庫,第一個參數(shù)是庫路徑,第二個參數(shù)是需要入庫的文件。
InsertRecords需要調用addFiles,addFiles是文件入庫的真正執(zhí)行者。AddFiles里有如下幾行重點代碼:
doc.add(Field.Keyword("filename",file.getName()));
注意,在Lucene里沒有嚴格意義上表,Lucene的表是通過Field類的方法動態(tài)構建的,比如Field.Keyword("filename",file.getName())就相當于在一條記錄加了一個字段,字段名為filename,該字段的內容為file.getName()。
常用的Field方法如下:
方法 |
切詞 |
索引 |
存儲 |
用途 |
Field.Text(String name, String value) |
Y |
Y |
Y |
標題,文章內容 |
Field.Text(String name, Reader value) |
Y |
Y |
N |
META信息 |
Field.Keyword(String name, String value) |
N |
Y |
Y |
作者 |
Field.UnIndexed(String name, String value) |
N |
N |
Y |
文件路徑 |
Field.UnStored(String name, String value) |
Y |
Y |
N |
與第二種類似 |
為了更深入的了解全文檢索庫,我們可以將全文檢索庫與通常的關系型數(shù)據(jù)庫(如Oracle,Mysql)作一下對比。
全文檢索庫對關系型數(shù)據(jù)庫對比 | ||
對比項 |
全文檢索庫(Lucene) |
關系型數(shù)據(jù)庫(Oracle) |
核心功能 |
以文本檢索為主,插入(insert)、刪除(delete)、修改(update)比較麻煩,適合于大文本塊的查詢。 |
插入(insert)、刪除(delete)、修改(update)十分方便,有專門的SQL命令,但對于大文本塊(如CLOB)類型的檢索效率低下。 |
庫 |
與Oracle類似,都可以建多個庫,且各個庫的存儲位置可以不同。 |
可以建多個庫,每個庫一般都有控制文件和數(shù)據(jù)文件等,比較復雜。 |
表 |
沒有嚴格的表的概念,比如Lucene的表只是由入庫時的定義字段松散組成。 |
有嚴格的表結構,有主鍵,有字段類型等。 |
記錄 |
由于沒有嚴格表的概念,所以記錄體現(xiàn)為一個對象,在Lucene里記錄對應的類是Document。 |
Record,與表結構對應。 |
字段 |
字段類型只有文本和日期兩種,字段一般不支持運算,更無函數(shù)功能。 在Lucene里字段的類是Field,如document(field1,field2…) |
字段類型豐富,功能強大。 record(field1,field2…) |
查詢結果集 |
在Lucene里表示查詢結果集的類是Hits,如hits(doc1,doc2,doc3…) |
在JDBC為例, Resultset(record1,record2,record3...) |
兩種庫對比圖如下:
Lucene doc(field1,field2..),doc(field1,field2..) 入庫: indexer Hits(doc(field1,field2..),doc(field1,field2..)...) 查詢: seracher Oracle record(field1,field2..),doc(field1,field2..) 入庫: insert rResultset(record(field1,field2..),doc(field1,field2..) 查詢: select
檢索源碼及說明
QueryRecords.java |
package com.holen.part1; import java.util.ArrayList; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Searcher; /** * @author Holen Chen * 檢索查詢 */ public class QueryRecords { public QueryRecords() { } /** * 檢索查詢,將結果集返回 * @param searchkey * @param dbpath * @param searchfield * @return */ public ArrayList queryRecords(String searchkey,String dbpath,String searchfield){ ArrayList list = null; try{ Searcher searcher = new IndexSearcher(dbpath); Query query = QueryParser.parse(searchkey,searchfield,new StandardAnalyzer()); Hits hits = searcher.search(query); if(hits != null){ list = new ArrayList(); int temp_hitslength = hits.length(); Document doc = null; for(int i = 0;i < temp_hitslength; i++){ doc = hits.doc(i); list.add(doc.get("filename")); } } }catch(Exception ex){ ex.printStackTrace(); } return list; } public static void main(String[] args) { QueryRecords temp = new QueryRecords(); ArrayList list = null; list = temp.queryRecords("holen","e:\\lucene\\holendb","content"); for(int i=0;i< list.size();i++){ System.out.println((String)list.get(i)); } } } |
說明:該類中Searcher負責查詢,并把查詢結果以Hits對象集方式返回,Hits好比JDBC中的RecordSet,Hits是Document的集合,每個Document相當于一條記錄,Document中包含一個或多個字段,可以通過Document.get(“字段名”)方法得到每個字段的內容。
通過這三個類,就完成了一個簡單的基于Lucene的全文檢索應用。
4.總結
Lucene十分精練純粹,就一個jar包,引入到你的工程中,調用其接口,就可以為你的應用增添全文檢索功能。
通過上一節(jié)的初步應用會發(fā)現(xiàn),Lucene使用起來很簡單,與JDBC有些類似,應用時重點掌握好IndexWriter,Document,Field,Searcher等幾個類即可。
Lucene的結構很清晰,每個package司職一項,比如org.apache.Lucene.search負責檢索,org.apache.Lucene.index索引,org.apache.Lucene.analysis切詞等,且Lucene的主要動作都采用了抽象類,擴展起來十分方便。
相對于一些商業(yè)化全文檢索,Lucene的入庫速度更快。因為它的存儲采取分步合并的方法,先建立小索引,待時機成熟才把小索引合并到大索引樹上。因此,我們在操作應用數(shù)據(jù)時可以同步進行全文檢索庫的操作而不會(或許很少)影響系統(tǒng)的效能。
Lucene性能穩(wěn)定,使用簡單,而且開源免費,有Apache基金在后面做支撐,資金和技術力量都十分雄厚,這兩年也一直是穩(wěn)步更新,每次新版本的推出,業(yè)界均爭相報導。
參考資料
1. Introduction to Text Indexing with Apache Jakarta Lucene(Otis Gospodnetic)
2. Lucene Introduction in Chinese(車東)
3. Lucene Tutorial(Steven J. Owens)
作者簡介
陳光 - J2EE項目經(jīng)理,熟悉EJB、XML,致力于Aapche Jakarta項目的應用與推廣,可通過holen@263.net與作者聯(lián)系。