文章目錄
原創作品,允許轉載,轉載時請務必以超連結形式標明文章
原始出處 、作者資訊和本人聲明。否則將追究法律責任。
作者:永恒の_☆ 地址:http://blog.csdn.net/chenghui0317/article/details/10052103一、Lucene的介紹
Lucene是一個全文檢索索引的架構,apache組織提供了一個用java實現的全文檢索搜尋引擎的開源項目,其功能非常的強大,api非常簡單,並且有了全文檢索索引的功能支援可以非常方便的實現根據關鍵字來搜尋整個應用系統的內容,大大提高了使用者的體驗效果。 使用Lucene來建立、搜尋功能和操作資料庫有一點像,這樣就可想而知,Lucene使用起來還是蠻方便的。
那麼為什麼要使用Lucene 呢? 因為如果沒有使用Lucene ,那麼就要根據某個關鍵字來搜尋資料庫表記錄, 就要使用like 一個字元一個字元去匹配,這樣子查詢的方式的要累壞程式員不說,並且查詢資料庫的效能開銷可想而知。
二、Lucene的執行流程
前面說了Lucene的操作方式和操作資料庫有點相似,所以如果要使用Lucene 就要先建立“資料庫”,然後往這個“資料表”中一行一行的插入資料,資料插入成功之後,就可以操作這張“資料表”,實現增刪改查操作了。
總的來說,可以這樣理解:
1、建立一個索引檔案目錄,然後把需要檢索的資訊 用Field 對應匹配的 封裝成一個Document文檔對象,將這個對象放入索引檔案目錄中,這裡既可以將索引存放到磁碟中,也可以放入記憶體中,如果放入記憶體,那麼程式關閉索引就沒有了,所以一般都是將索引放入磁碟中;
2、如果發現資訊有問題需要刪除,那麼索引檔案也要刪除,否則檢索的時候還是會查詢得到,這個時候需要根據索引id去刪除對應的索引;
3、如果發現資訊被更新了,那麼索引檔案也要更新,這個時候需要先將舊的索引刪除然後添加新的索引;
4、最後重頭戲是全文檢索搜尋了,這和查詢資料庫一樣,先需要建立索引讀取對象,然後封裝Query查詢對象,調用search()方法 得到檢索結果。
三、使用Lucene的準備條件
lucene-core-3.6.0.jar
lucene-highlighter-3.6.0.jar
lucene-memory-3.6.0.jar
:http://download.csdn.net/detail/ch656409110/5971413
四、使用Lucene實戰
1、使用Lucene將索引 寫入記憶體
實現的思路如下:
<1>建立了記憶體目錄對象RAMDirectory 和 索引寫入器IndexWriter ;
<2>利用索引寫入器將指定的資料存入記憶體目錄對象中;
<3>建立IndexSearch 索引查詢對象,然後根據關鍵字封裝Query查詢對象;
<4>調用search()方法,將查詢的結果返回給TopDocs ,迭代裡面所有的Document對象,顯示查詢結果;
<5>關閉IndexWriter 寫入器,關閉RAMDirectory目錄對象。
具體代碼如下:
package com.lucene.test;import java.io.IOException;import org.apache.lucene.analysis.SimpleAnalyzer;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.index.Term;import org.apache.lucene.search.IndexSearcher;import org.apache.lucene.search.Query;import org.apache.lucene.search.TermQuery;import org.apache.lucene.search.TopDocs;import org.apache.lucene.store.RAMDirectory;/** * lucene 檢索記憶體索引 非常簡單的例子 * * @author Administrator * */public class RAMDirectoryDemo { public static void main(String[] args) throws IOException { long startTime = System.currentTimeMillis(); System.out.println("*****************檢索開始**********************"); // 建立一個記憶體目錄對象,所以這裡產生的索引不會放在磁碟中,而是在記憶體中。 RAMDirectory directory = new RAMDirectory(); /* * 建立索引寫入對象,該對象既可以把索引寫入到磁碟中也可以寫入到記憶體中。 參數說明: * public IndexWriter(Directory d, Analyzer a, boolean create, MaxFieldLength mfl) * directory:目錄對象,也可以是FSDirectory 磁碟目錄對象 * analyzer:分詞器,分詞器就是將檢索的關鍵字分割成一組組片語, 它是lucene檢索查詢的一大特色之一, new SimpleAnalyzer()這個是lucene內建的最為簡單的分詞器; create: 是否建立,這裡肯定要設為true; * maxFieldLength:這個是分詞器拆分最大長度,因為各種不同類型的分詞器拆分的字元顆粒細化程度不一樣,所以需要設定一個最長的拆分長度。IndexWriter.MaxFieldLength.UNLIMITED表示無限制; */ IndexWriter writer = new IndexWriter(directory, new SimpleAnalyzer(),true, IndexWriter.MaxFieldLength.UNLIMITED); // 建立Document 文檔對象,在lucene中建立的索引可以看成資料庫中的一張表,表中也可以有欄位,往裡面新增內容之後可以根據欄位去匹配查詢 // 下面建立的doc對象中添加了三個欄位,分別為name,sex,dosomething, Document doc = new Document(); /* * 參數說明 public Field(String name, String value, Store store, Index index) * name : 欄位名稱 * value : 欄位的值 store : * Field.Store.YES:儲存欄位值(未分詞前的欄位值) Field.Store.NO:不儲存,儲存與索引沒有關係 * Field.Store.COMPRESS:壓縮儲存,用於長文本或二進位,但效能受損 * index : 建立索引的方式,是否建立分詞等等 * Field.Index.ANALYZED:分詞建索引 * Field.Index.ANALYZED_NO_NORMS:分詞建索引,但是Field的值不像通常那樣被儲存,而是只取一個byte,這樣節約儲存空間 * Field.Index.NOT_ANALYZED:不分詞且索引 ,一旦指定為這種類型後將會被lucenn錄入索引中,但不會被作為關鍵搜尋,除非輸入所有的關鍵字 * Field.Index.NOT_ANALYZED_NO_NORMS:不分詞建索引,Field的值去一個byte儲存 */ doc.add(new Field("name", "Chenghui", Field.Store.YES,Field.Index.ANALYZED)); doc.add(new Field("sex", "男的", Field.Store.YES,Field.Index.NOT_ANALYZED)); doc.add(new Field("dosometing", "I am learning lucene ",Field.Store.YES, Field.Index.ANALYZED)); writer.addDocument(doc); writer.close(); // 這裡可以提前關閉,因為dictory 寫入記憶體之後 與IndexWriter 沒有任何關係了 // 因為索引放在記憶體中,所以存放進去之後要立馬測試,否則,關閉應用程式之後就檢索不到了 // 建立IndexSearcher 檢索索引的對象,裡面要傳遞上面寫入的記憶體目錄對象directory IndexSearcher searcher = new IndexSearcher(directory); // 根據搜尋索引鍵 封裝一個term組合對象,然後封裝成Query查詢對象 // dosometing是上面定義的欄位,lucene是檢索的關鍵字 Query query = new TermQuery(new Term("dosometing", "lucene")); // Query query = new TermQuery(new Term("sex", "男")); // Query query = new TermQuery(new Term("name", "cheng")); // 去索引目錄中查詢,返回的是TopDocs對象,裡面存放的就是上面放的document文檔對象 TopDocs rs = searcher.search(query, null, 10); long endTime = System.currentTimeMillis(); System.out.println("總共花費" + (endTime - startTime) + "毫秒,檢索到" + rs.totalHits + "條記錄。"); for (int i = 0; i < rs.scoreDocs.length; i++) { // rs.scoreDocs[i].doc 是擷取索引中的標誌位id, 從0開始記錄 Document firstHit = searcher.doc(rs.scoreDocs[i].doc); System.out.println("name:" + firstHit.getField("name").stringValue()); System.out.println("sex:" + firstHit.getField("sex").stringValue()); System.out.println("dosomething:" + firstHit.getField("dosometing").stringValue()); } writer.close(); directory.close(); System.out.println("*****************檢索結束**********************"); }}
運行結果如下:
由上可知: 上面根據“lucene”關鍵字查詢成功了,返回的是一個個Document封裝的對象。
另外 如果在建立索引寫入器IndexWriter的時候 create 參數指定為false的話 ,會報錯,找不到索引檔案,因為每一次讀取都是”現存現取“的模式,具體如下:
Exception in thread "main" org.apache.lucene.index.IndexNotFoundException: no segments* file found in org.apache.lucene.store.RAMDirectory@156ee8e lockFactory=org.apache.lucene.store.SingleInstanceLockFactory@47b480:
files: []
根據dosomething可以查詢成功,同樣根據sex欄位 和name欄位一樣可以實現查詢功能。
<1>如果把 Query query = new TermQuery(new Term("sex", "男")); 取消注釋 ,然後查詢一下:
結果發現根本沒有記錄,這是因為在產生索引的時候指定的sex欄位 是Field.Index.NOT_ANALYZED 類型的,所以Lucene沒有為該欄位建立索引,也就不能根據sex欄位去查詢。
<2>然後 將sex 欄位改為Field.Index.ANALYZED類型的,再查詢一下:
結果發現依然沒有記錄,為什嗎?
這是因為使用的分詞器SimpleAnalyzer 沒有那麼智能化,它只會對關鍵字中包含空格的片語進行分詞和匹配,簡而言之:如果“中國”是搜尋索引鍵,那麼它只會匹配索引表中對應欄位中包含“ 中國 ”這樣的片語,而包含“中”或者“國”的漢字不會被匹配,
記住這裡面前後都有空格分開。同樣的,如果建立的索引中資料有“我是中國人” ,那麼要根據“中國“這個關鍵字根本匹配不到,因為SimpleAnalyzer 這個分詞器只會去匹配 以空格分開的片語,所以要想匹配成功,那麼添加索引的資料應該改為“我是 中國 人”,這樣才會被檢索到。
所以所以,要想使用”男“這個關鍵字匹配成功,就需要在添加索引的時候 改為”男 的“才可以。
既然中文漢字 是這樣,試試看英文字母 是否亦如此。
果不其然:
<1>如果錄入name欄位的索引為”chenghui“ 關鍵字指定為"cheng"搜不到;
<2>如果錄入name欄位的索引為”cheng hui“ 關鍵字指定為"cheng"可以搜到;
<3>如果錄入name欄位的索引為”cheng hui“ 關鍵字指定為"Cheng"搜不到;
<4>如果錄入name欄位的索引為”Cheng hui“ 關鍵字指定為"Cheng"搜不到;
<5>如果錄入name欄位的索引為”Cheng hui“ 關鍵字指定為"cheng"可以搜到;
由此可見: 索引錄入的時候會統一轉換成小寫,但是關鍵字 沒有轉換成小寫去匹配,這才導致大小寫匹配不到的情況。
所以英文與數字和中文一樣 不是那麼人性化,並且我檢索的時候連字母的大小寫都沒有區分,同時 數字 也是一樣子的問題, 可見Lucene對其搜尋的不完善性。
有沒有解決方案呢? 有,上面例子使用的是SimpleAnalyzer 分詞器,這個分詞器是一段一段話進行分,現在介紹另外一個分詞器StandardAnalyzer分詞器,標準分詞拿來分中文和ChineseAnalyzer一樣的效果。
把上述代碼中傳遞的分詞器對象換成 new StandardAnalyzer(Version.LUCENE_36) ,試試效果,發現 StandardAnalyzer 在 SimpleAnalyzer 的基礎上進行了最佳化,遺憾的是只是中文方面的,比如:
<1>如果錄入sex索引為”男的“ 關鍵字指定為"男" 可以搜到;
但是僅僅這樣,遠遠不能滿足全文檢索索引的需求,這就需要使用更加進階的分詞器來實現該功能。