Lucene是一個開源的搜索引擎, 或者叫全文索引工具, 用於快速查找, 而且可以獲得一個匹配度得分;
比如我們直接使用sql進行搜索的時候, 可能會使用Like關鍵詞
但是這樣有兩個不便之處, 一是當數據太多時, 會比較慢, 二是這樣無法得到一些基於關鍵詞的匹配相似度得分;
用lucene可以實現上面兩點;
那麼直接開始做一個簡單的搜索的demo吧~
一.創建索引
假設現在已經有了一些數據, 放在了一個list裏面:
List<String> productNames = new ArrayList<>();
productNames.add("飛利浦led燈泡e27螺口暖白球泡燈家用照明超亮節能燈泡轉色溫燈泡");
productNames.add("飛利浦led燈泡e14螺口蠟燭燈泡3W尖泡拉尾節能燈泡暖黃光源Lamp");
productNames.add("雷士照明 LED燈泡 e27大螺口節能燈3W球泡燈 Lamp led節能燈泡");
productNames.add("飛利浦 led燈泡 e27螺口家用3w暖白球泡燈節能燈5W燈泡LED單燈7w");
productNames.add("飛利浦led小球泡e14螺口4.5w透明款led節能燈泡照明光源lamp單燈");
productNames.add("飛利浦蒲公英護眼檯燈工作學習閱讀節能燈具30508帶光源");
productNames.add("歐普照明led燈泡蠟燭節能燈泡e14螺口球泡燈超亮照明單燈光源");
productNames.add("歐普照明led燈泡節能燈泡超亮光源e14e27螺旋螺口小球泡暖黃家用");
productNames.add("聚歐普照明led燈泡節能燈泡e27螺口球泡家用led照明單燈超亮光源");
Directory index = createIndex(analyzer, productNames);
最後一行的createIndex方法實現如下:
private static Directory createIndex(IKAnalyzer analyzer, List<String> products) throws IOException {
Directory index = new RAMDirectory();
IndexWriterConfig config = new IndexWriterConfig(analyzer);
IndexWriter writer = new IndexWriter(index, config);
for (String name : products) {
addDoc(writer, name);
}
writer.close();
return index;
}
這裏傳入的參數有兩個, 第一個是中文分詞器, 第二個是存入的數據;
第一步是創建一個Dictionary, 然後構建一個IndexWriter用於寫入, 這裏要給這個IndexWritter使用一個Config因爲需要使用中文分詞器對傳入的數據進行解析, 然後創建好writer之後循環寫入數據即可, 這裏的addDoc方法實現如下, 最後關閉writer即可:
private static void addDoc(IndexWriter w, String name) throws IOException {
Document doc = new Document();
doc.add(new TextField("name", name, Field.Store.YES));
w.addDocument(doc);
}
在這裏傳入一個寫入器;
其實這裏是創建一個新的Document然後把這個document用writer寫入到之前創建的dictionary裏面, 此處的document是可以有多個Field的, 但是這裏只傳入了一項數據, 所以只需要用到一個TextField叫name;
然後到這裏爲止, 我們的數據部分就準備完成了, 然後下面我們開始執行一次查詢
二.執行一次查詢
先構建一個查詢器
首先我們獲取到用戶提供的關鍵詞, 然後根據這個關鍵詞來構建一個查詢器, 大概代碼如下:
String keyword = "護眼帶光源";
Query query = new QueryParser("name", analyzer).parse(keyword);
這裏我們首先獲得了一個keyword, 然後構造了一個QueryParser解析器, 然後傳入一個analyzer(就是之前創建的中文分詞器), 然後我們要對name進行搜索, 解析keyword, 這樣一個搜索的查詢器就構建好了;
然後進行搜索
IndexReader reader = DirectoryReader.open(index);
IndexSearcher searcher = new IndexSearcher(reader);
int numberPerPage = 1000;
System.out.printf("當前一共有%d條數據%n",productNames.size());
System.out.printf("查詢關鍵字是:\"%s\"%n",keyword);
ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;
首先要構造一個IndexReader對之前創建的Dictionary進行讀取, 這裏調用的是DirectoryReader的靜態方法, 然後使用IndexSearcher來搜索, 設定每頁要顯示多少條數據, 然後調用searcher的search方法, 這裏的返回的是一個scoreDoc的數組;
然後顯示搜索結果
其實這部分就是要對剛纔獲得的結果, 也就是那個scoreDoc這個數組進行解析, 獲得內容;
大概代碼如下:
private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer)
throws Exception {
System.out.println("找到 " + hits.length + " 個命中.");
System.out.println("序號\t匹配度得分\t結果");
for (int i = 0; i < hits.length; ++i) {
ScoreDoc scoreDoc= hits[i];
int docId = scoreDoc.doc;
Document d = searcher.doc(docId);
List<IndexableField> fields = d.getFields();
System.out.print((i + 1));
System.out.print("\t" + scoreDoc.score);
for (IndexableField f : fields) {
System.out.print("\t" + d.get(f.name()));
}
System.out.println();
}
}
可以看到, 這裏直接取scoreDoc.doc獲得的是在原來的Dictionary中的document id, 然後要利用searcher的doc方法來獲得這個document的內容, 然後此處的document中只有一個field就是name, 但是往往可能會有多個field, 所以這裏用了遍歷所有的field來輸出內容, 兼容性更好
整理
所以Lucene的基本流程是這樣的:
第一步創建索引
獲得數據 → 創建Dictionary
第二步開始查詢
查詢解析器處理關鍵詞 → 查詢器 → IndexSearcher(IndexReader) → ScoreDoc
一些花裏胡哨的東西
1.分頁查詢
這個lucene可以使用分頁查詢, 可以看到, 在搜索這一步, 有這個操作
ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;
這一步就是把符合的hits的scoredoc全部存到了這個hits數組裏面, 也就相當於保存到了內存中, 這樣在數據量較大的時候是不太好的
所以可以使用分頁查詢, 比如每頁10條數據, 一共20頁, 我要查第7頁的那10條數據
上面的方法就是, 我把這20頁的200條數據全部存到內存中, 然後去讀取我要的;
但是還可以我先查找到第69條數據, 然後使用searchAfter方法, 查找後面的10條數據, 這樣就不用把所有數據都放到內存裏了, 給個大概的demo把~
private static ScoreDoc[] pageSearch2(Query query, IndexSearcher searcher, int pageNow, int pageSize)throws IOException{
int start = (pageNow-1)*pageSize;
if(start == 0){
return searcher.search(query, pageSize).scoreDocs;
}
TopDocs topDocs = searcher.search(query, start);
ScoreDoc preScoreDoc = topDocs.scoreDocs[start-1];
topDocs = searcher.searchAfter(preScoreDoc, query, pageSize);
return topDocs.scoreDocs;
}
2.索引的增加刪除修改
Dictionary在創建之後也是可以修改的
增加的方法就和前面創建的時候一樣, 還是用writer來寫進去add就好了;
刪除可以用deleteDocument方法, 類似這樣:
//刪除id=51173的數據
IndexWriterConfig config = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(index, config);
indexWriter.deleteDocuments(new Term("id", "51173"));
indexWriter.commit();
indexWriter.close();
然後lucene還支持其他的一些方式的刪除, 比如這樣的:
DeleteDocuments(Query query):根據Query條件來刪除單個或多個Document
DeleteDocuments(Query[] queries):根據Query條件來刪除單個或多個Document
DeleteDocuments(Term term):根據Term來刪除單個或多個Document
DeleteDocuments(Term[] terms):根據Term來刪除單個或多個Document
DeleteAll():刪除所有的Document
然後修改也差不多, demo給一個:
IndexWriterConfig config = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(index, config);
Document doc = new Document();
doc.add(new TextField("id", "51173", Field.Store.YES));
doc.add(new TextField("name", "神鞭,鞭沒了,神還在", Field.Store.YES));
doc.add(new TextField("category", "道具", Field.Store.YES));
doc.add(new TextField("price", "998", Field.Store.YES));
doc.add(new TextField("place", "南海羣島", Field.Store.YES));
doc.add(new TextField("code", "888888", Field.Store.YES));
indexWriter.updateDocument(new Term("id", "51173"), doc );
indexWriter.commit();
indexWriter.close();
更多內容可以到HOW2J網站學習