Lucene4.X 高級應用
Lucene 簡介以及使用
Lucene,一個基于 Java 的開源的全文搜索工具包,可以方便的嵌入到各種應用系統中,實現針對應用的全文索引以及檢索功能。目前是 Apache jakarta 項目組的一個子項目,它的目的是為程序員提供工具包,讓程序員利用工具包里的強大接口來完成全文檢索。下面我們將以 Lucene4.7 版本為例,為您詳細講解索引的創建、創建時的參數配置、Lucene4.7 版本的各種 query 查詢、Lucene 神器 Luke 的使用等內容。
準備工作
本文需要的 jar 包:
lucene-analyzers-common-4.7.0.jar
lucene-core-4.7.0.jar
lucene-queryparser-4.7.0.jar
Lucene 相關問題可以參考 Lucene 的官方 API 。
Lucene 常用包 官方網站 下載。
重要關鍵字介紹
IndexWriter:用于處理索引,如增加、更改或者刪除索引。
FSDirectory:索引目錄,除此之外還有一個 Ramdirectry。FSDirectory 是將索引創建到磁盤里,而 Ramdirectry 是將索引創建到內存里。
IndexWriterConfig:這里可配置版本號,分詞器,打開模式等等,合理的應用該對象屬性可以大大提高創建索引的性能。
Document:文檔,我們將每個字段放在 document 里。
Field :域,類似數據庫中的 column。
回頁首
Lucene 實戰
創建索引
首先我們介紹下如何創建索引。相關步驟分為:建立索引器 IndexWriter,建立文檔對象 Document,建立信息字段對象 Field,將 Field 添加到 Document,將 Document 添加到 IndexWriter 里面,最后不要忘記關閉 IndexWriter。
清單 1. 建立索引
package lucene; …… public class IndexUtil { private String[] idArr = {"1","2","3","4","5","6"}; private String[] emailArr = {"abc@us.ibm.com","ert@cn.ibm.com","lucy@us.ibm.com", "rock@cn.ibm.com","test@126.com","deploy@163.com"}; private String[] contentArr = { "welcome to Lucene,I am abc","This is ert,I am from China", "I'm Lucy,I am english","I work in IBM", "I am a tester","I like Lucene in action" }; private String[] nameArr = {"abc","ert","lucy","rock","test","deploy"}; private Directory directory = null; public void index() { IndexWriter writer = null; try { directory = FSDirectory.open(new File("C:/lucene/index02")); IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_47, new StandardAnalyzer(Version.LUCENE_47)); conf.setOpenMode(OpenMode.CREATE_OR_APPEND); LogMergePolicy mergePolicy = new LogDocMergePolicy(); mergePolicy.setMergeFactor(10); mergePolicy.setMaxMergeDocs(10); conf.setMaxBufferedDocs(10); writer = new IndexWriter(directory, conf); Document doc = null; int date = 1; for(int i=0;i<idArr.length;i++) { doc = new Document(); doc.add(new StringField("id",idArr[i],Field.Store.YES)); doc.add(new StringField("email",emailArr[i],Field.Store.YES)); doc.add(new StringField("content",contentArr[i],Field.Store.YES)); doc.add(new StringField("name",nameArr[i],Field.Store.YES)); doc.add(new StringField("date","2014120"+date+“222222”,Field.Store.YES)); writer.addDocument(doc); date++; } //新的版本對 Field 進行了更改,StringField 索引但是不分詞、StoreField 至存儲不索引、TextField 索引并分詞 } catch (CorruptIndexException e) { e.printStackTrace(); } catch (LockObtainFailedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if(writer!=null)writer.close(); } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]){ IndexUtil indexUtil = new IndexUtil(); indexUtil.index(); } }
參數解釋:
SetMergeFactor(合并因子),是控制 segment 合并頻率的,其決定了一個索引塊中包括多少個文檔,當硬盤上的索引塊達到這個值時,將它們合并成一個較大的索引塊。當 MergeFactor 值較大時,生成索引的速度較快。MergeFactor 的默認值是 10。
SetMaxMergeDocs 最大合并文檔數,默認是 Integer.MAX_VALUE。設置 segment 最大合并文檔 (Document) 數值較小越有利于追加索引的速度,值較大, 越適合批量建立索引和更快的搜索。
setMaxBufferedDocs 最大緩存文檔數,是控制寫入一個新的 segment 前內存中保存的 document 的數目,設置較大的數目可以加快建索引速度,默認為 10。
在創建創 IndexWriter 實例的時候應注意以下幾個地方:
- 盡量保持 IndexWriter 在全局中只有一個實例,因為一個 directory 中只允許一個 IndexWriter 實例訪問,如果兩個或者兩個以上的實例同時訪問一個 directory 會出現 Lock obtain timed Out 異常, 在文件夾里會出現一個 write.lock 文件。
- 在配置 LogMergePolicy 的時候不要盲目的去設置,要根據物理機器的配置來進行次測試,來達到一個理想的配置。
查詢索引
當我們創建好索引后,就可以利用 Lucene 進行索引查詢,Lucene 提供了多個查詢功能,下面我們進行簡單介紹。
Query:一個查詢的抽象類,有多個子類實現,TermQuery, BooleanQuery, PrefixQuery ,WildcardQuery 等。
Term:是搜索的基本單位,一個 Term 是由兩個 String 的 field 組成。比如,Term("name",“rock”), 此時該語句是查詢 name 為 rock 的條件。
IndexSearcher:當索引建立好后,用該對象進行查詢。該對象只能以只讀的方式打開索引,所以多個 IndexSearcher 對象可以查詢一個索引目錄。我們要注意一下這個現象。
在介紹幾種查詢方式之前,首先要初始化 directory:
directory = FSDirectory.open(new File("C:/lucene/index02 "));
其次獲取 IndexSearcher:
public IndexSearcher getSearcher() { IndexReader reader = null; try { reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); return searcher; } catch (IOException e) { e.printStackTrace(); } return null; }
清單 2. 使用 TermQuery 搜索
public void searchByTerm(String field, String name, int num) { try { IndexSearcher searcher = getSearcher(); Query query = new TermQuery(new Term(field, name)); TopDocs tds = searcher.search(query, num); System.out.println("count:" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
結果:
count:1
docId:4
name:rock
email:rock@cn.ibm.com
date:2014-12-4
說明:
TermQuery 是 Lucene 查詢中最基本的一種查詢,它只能針對一個字段進行查詢。
清單 3. 范圍查詢 RangeQuery (搜索指定范圍的數據)
public void searchByTermRange(String field,String start,String end,int num) { try { IndexSearcher searcher = getSearcher(); BytesRef lowerTerm = new BytesRef(start); BytesRef upperTerm = new BytesRef(end); Query query = new TermRangeQuery(field,lowerTerm,upperTerm,true, true); TopDocs tds = searcher.search(query, num); System.out.println("count:"+tds.totalHits); for(ScoreDoc sd:tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
結果:
count:3
docId:1
name:abc
email:abc@us.ibm.com
date:20141201222222
docId:2
name:ert
email:ert@cn.ibm.com
date:20141202222222
docId:3
name:lucy
email:lucy@us.ibm.com
date:20141203222222
說明:
TermRangeQuery query=new TermRangeQuery(字段名, 起始值, 終止值, 起始值是否包含邊界, 終止值是否包含邊界)。
清單 4.PrefixQuery 前綴查詢
//查詢以 ro 開頭的 name public void searchByPrefix(String field, String value, int num) { try { IndexSearcher searcher = getSearcher(); Query query = new PrefixQuery(new Term(field, value)); TopDocs tds = searcher.search(query, num); System.out.println("count:" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
結果:
count:1
docId:4
name:rock
email:rock@cn.ibm.com
date:20141204222222
說明:
前綴查詢, 搜索匹配開始位置的數據類似百度的輸入框。
清單 5.WildcardQuery 通配符查詢
//查詢 email 是 test 的 public void searchByWildcard(String field, String value, int num) { try { IndexSearcher searcher = getSearcher(); Query query = new WildcardQuery(new Term(field, value)); TopDocs tds = searcher.search(query, num); System.out.println("count" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
結果:
count1
docId:5
name:test
email:test@126.com
date:20141205222222
說明:
通配符分為兩種,“*”和“?”,“*”表示任何字符,“?”表示任意一個字符。
Term term=new Term(字段名, 搜索關鍵字+通配符)。
清單 6.FuzzyQuery 模糊搜索
public void searchByFuzzy(int num) { try { IndexSearcher searcher = getSearcher(); FuzzyQuery query = new FuzzyQuery(new Term("name","acc"),1,1); //System.out.println(query.getPrefixLength()); TopDocs tds = searcher.search(query, num); System.out.println("count:"+tds.totalHits); for(ScoreDoc sd:tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
結果:
count:1
docId:1
name:abc
email:abc@us.ibm.com
date:20141201222222
說明:
FuzzyQuery(new Term("name","acc"),1,1),需要 3 個參數,第一個參數是詞條對象,第二個參數是 levenshtein 算法的最小相似度,第三個參數是指與多少個前綴字符匹配。
清單 7.BooleanQuery 查詢
public void searchByBoolean(int num) { try { IndexSearcher searcher = getSearcher(); BooleanQuery query = new BooleanQuery(); query.add(new TermQuery(new Term("name", "abc")),BooleanClause.Occur.SHOULD); query.add(new TermQuery(new Term("email","lucy@us.ibm.com")), BooleanClause.Occur.SHOULD); TopDocs tds = searcher.search(query, num); System.out.println("count" + tds.totalHits); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id")); System.out.println("name:"+doc.get("name")); System.out.println("email:"+doc.get("email")); System.out.println("date:"+doc.get("date")); } } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }結果:
count2
docId:1
name:abc
email:abc@us.ibm.com
date:20141201222222
docId:3
name:lucy
email:lucy@us.ibm.com
date:20141203222222
說明:
BooleanQuery,也就是組合查詢,允許進行邏輯 AND、OR 或 NOT 的組合,通過 BooleanQuery 的 add 方法將一個查詢子句增加到某個 BooleanQuery 對象中。
BooleanClause.Occur.MUST:必須包含,相當于邏輯運算的與
BooleanClause.Occur.MUST_NOT:必須不包含,相當于邏輯運算的非
BooleanClause.Occur.SHOULD:可以包含,相當于邏輯運算的或
清單 8. 分頁查詢
private static void testPageSearch1(int currentPage) { int PAGE_SIZE = 10; IndexReader reader = null; try { reader = DirectoryReader.open(FSDirectory.open(new File(""))); IndexSearcher searcher = new IndexSearcher(reader); Query query = new TermQuery(new Term("name", "rock")); TopDocs topDocs = searcher.search(query, currentPage * PAGE_SIZE); ScoreDoc[] hits = topDocs.scoreDocs; int endNuM = Math.min(topDocs.totalHits, currentPage * PAGE_SIZE); for (int i = (currentPage - 1) * PAGE_SIZE; i < endNuM; i++) { Document doc = searcher.doc(hits[i].doc); System.out.print(doc.get("USERNAME")); } } catch (IOException e) { e.printStackTrace(); } } //在 Lucene 的 3.5 以后的版本,Lucene 的 API 里提供了一個分頁方法 searchafter。 private ScoreDoc getLastScoreDoc(int pageIndex, int pageSize, Query query, IndexSearcher searcher) throws IOException { if (pageIndex == 1) return null; int num = pageSize * (pageIndex - 1); TopDocs tds = searcher.search(query, num); return tds.scoreDocs[num - 1]; } public void searchPageByAfter(String query, int pageIndex, int pageSize) { try { IndexSearcher searcher = getSearcher(); QueryParser parser = new QueryParser(Version.LUCENE_47, "content",new StandardAnalyzer(Version.LUCENE_47)); Query q = parser.parse(query); ScoreDoc lastSd = getLastScoreDoc(pageIndex, pageSize, q, searcher); TopDocs tds = searcher.searchAfter(lastSd, q, pageSize); for (ScoreDoc sd : tds.scoreDocs) { Document doc = searcher.doc(sd.doc); System.out.println("docId:"+doc.get("id") + ",name:" + doc.get("name")+",email:"+ doc.get("email") ); } } catch (IOException e) { e.printStackTrace(); } catch (org.apache.lucene.queryparser.classic.ParseException e) { e.printStackTrace(); } }
排序
Lucene 除了提供大量的查詢功能外,還提供了一個可改變查詢結果順序的類 Sort,用戶可根據自己的需求進行 Sort 排序設置。
Sort sort = new Sort(); ortField sf=new SortField("name",Type.STRING_VAL, false);
以上語句表示根據 name 進行排序,false 代表升序,如果是 true 代表降序,可以有多個 SortField,利用 Sort 的 sort.setSort(sf,sf1...) 將每個 SortField 添加到 sort 中,最后返回按 sort 進行排序的搜索結果。
TopDocs topDocs = searcher.search(query, currentPage * PAGE_SIZE,sort);
回頁首
Luke 的使用
Luke 是 Lucene 搜索引擎的查看、診斷工具,如果在進程中出現搜索不到或者搜索出的結果與預期不匹配時可用 Luke 工具進行查詢、修改和調試。使用 Luke 的前提是在創建索引的時候使用的是 FSDirectory。
點此進行 LUKE 下載 ,同時,Luke 需要安裝 Java 1.5 或更高版本。
下載的版本必須要與 Lucene 的版本相匹配,否則會造成索引目錄打不開現象。
以下是以 lukeall-4.7.1.jar 來進行演示的結果,雙擊該 jar 包進行打開,如打不開,可選擇以 cmd 命令行的方式進行打開,java -jar lukeall-4.7.1.jar。打開后,顯示界面如下圖所示:
圖 1.lukeall 首頁面

接下來,需要選擇 index director 的路徑,圖 1 中紅色箭頭所示,然后點擊 OK。詳細索引信息見圖 2.
圖 2.Luke-Overview

上圖詳細的展示了 Overview 選項下的一些主要索引信息,例如 field 數量,document 的數量和 term 的數量等信息。
圖 3 是 Luke 下索引的具體列表展示,可以得到每個 term 的詳細信息。
圖 3.Luke-Search

Luke 除了可以宏觀的看到索引信息外,還提供了可視化的界面查詢,查詢語法是 [字段名:內容],如圖 3 左上方所示。
Luke 是一個開源工具,開發人員也可以通過插件和腳本進行自定義功能定制和擴展。
回頁首
結束語
本文主要介紹了如何去創建索引以及創建索引時 LogMergePolicy 的配置,合理的配置 LogMergePolicy 可以提高創建索引的效率。由于一個索引文件夾只能允許一個 IndexWriter 訪問,所以最好將 IndexWriter 寫成單例模式,保持全局只有一個 IndexWriter,最后一定要記得關閉 IndexWriter。. 然后依次介紹了 Lucene 里的各種 query 查詢,要根據項目的實際需要選擇相應的 query。還有就是要注意 Directory 的用法,API 里提供了各種 Directory,但要根據自己的實際情況選擇最佳的 Directory。同時,為了減少內存開支,最好只實例化一個 Directory,因為每次打開 Directory 都需要消耗大量內存。新版本的 Lucene 提供了 SearchManager 去管理 IndexReader 和 IndexSearcher,所以不需要大家再去實現這兩個對象的單例模式了。最后介紹了 Lucene 索引查詢工具,該工具可以幫助開發人員快速、有效的進行索引數據的查看、添加、修改或刪除。希望本文章可以為 Lucene 的學習使用人員提供一些幫助。
原文 http://www.ibm.com/developerworks/cn/java/j-lo-Lucene/index.html?ca=drs-