SpringCloud學習筆記024---SpringBoot整合Lucene實現全文檢索_分詞_索引_更新_刪除文件_詞條搜尋_多條件查詢
先看程式碼實現,下面有lucene介紹:
測試用例
Github 程式碼
程式碼我已放到 Github ,匯入spring-boot-lucene-demo
專案
新增依賴
<!--對分詞索引查詢解析--> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>7.1.0</version> </dependency> <!--高亮 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>7.1.0</version> </dependency> <!--smartcn 中文分詞器 SmartChineseAnalyzer smartcn分詞器 需要lucene依賴 且和lucene版本同步--> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-smartcn</artifactId> <version>7.1.0</version> </dependency> <!--ik-analyzer 中文分詞器--> <dependency> <groupId>cn.bestwu</groupId> <artifactId>ik-analyzers</artifactId> <version>5.1.0</version> </dependency> <!--MMSeg4j 分詞器--> <dependency> <groupId>com.chenlb.mmseg4j</groupId> <artifactId>mmseg4j-solr</artifactId> <version>2.4.0</version> <exclusions> <exclusion> <groupId>org.apache.solr</groupId> <artifactId>solr-core</artifactId> </exclusion> </exclusions> </dependency>
配置 lucene
private Directory directory; private IndexReader indexReader; private IndexSearcher indexSearcher; @Before public void setUp() throws IOException { //索引存放的位置,設定在當前目錄中 directory = FSDirectory.open(Paths.get("indexDir/")); //建立索引的讀取器 indexReader = DirectoryReader.open(directory); //建立一個索引的查詢器,來檢索索引庫 indexSearcher = new IndexSearcher(indexReader); } @After public void tearDown() throws Exception { indexReader.close(); } ** * 執行查詢,並列印查詢到的記錄數 * * @param query * @throws IOException */ public void executeQuery(Query query) throws IOException { TopDocs topDocs = indexSearcher.search(query, 100); //列印查詢到的記錄數 System.out.println("總共查詢到" + topDocs.totalHits + "個文件"); for (ScoreDoc scoreDoc : topDocs.scoreDocs) { //取得對應的文件物件 Document document = indexSearcher.doc(scoreDoc.doc); System.out.println("id:" + document.get("id")); System.out.println("title:" + document.get("title")); System.out.println("content:" + document.get("content")); } } /** * 分詞列印 * * @param analyzer * @param text * @throws IOException */ public void printAnalyzerDoc(Analyzer analyzer, String text) throws IOException { TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(text)); CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); try { tokenStream.reset(); while (tokenStream.incrementToken()) { System.out.println(charTermAttribute.toString()); } tokenStream.end(); } finally { tokenStream.close(); analyzer.close(); } }
建立索引
@Test public void indexWriterTest() throws IOException { long start = System.currentTimeMillis(); //索引存放的位置,設定在當前目錄中 Directory directory = FSDirectory.open(Paths.get("indexDir/")); //在 6.6 以上版本中 version 不再是必要的,並且,存在無參構造方法,可以直接使用預設的 StandardAnalyzer 分詞器。 Version version = Version.LUCENE_7_1_0; //Analyzer analyzer = new StandardAnalyzer(); // 標準分詞器,適用於英文 //Analyzer analyzer = new SmartChineseAnalyzer();//中文分詞 //Analyzer analyzer = new ComplexAnalyzer();//中文分詞 //Analyzer analyzer = new IKAnalyzer();//中文分詞 Analyzer analyzer = new IKAnalyzer();//中文分詞 //建立索引寫入配置 IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer); //建立索引寫入物件 IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig); //建立Document物件,儲存索引 Document doc = new Document(); int id = 1; //將欄位加入到doc中 doc.add(new IntPoint("id", id)); doc.add(new StringField("title", "Spark", Field.Store.YES)); doc.add(new TextField("content", "Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎", Field.Store.YES)); doc.add(new StoredField("id", id)); //將doc物件儲存到索引庫中 indexWriter.addDocument(doc); indexWriter.commit(); //關閉流 indexWriter.close(); long end = System.currentTimeMillis(); System.out.println("索引花費了" + (end - start) + " 毫秒"); }
響應
17:58:14.655 [main] DEBUG org.wltea.analyzer.dic.Dictionary - 載入擴充套件詞典:ext.dic
17:58:14.660 [main] DEBUG org.wltea.analyzer.dic.Dictionary - 載入擴充套件停止詞典:stopword.dic
索引花費了879 毫秒
刪除文件
@Test
public void deleteDocumentsTest() throws IOException {
//Analyzer analyzer = new StandardAnalyzer(); // 標準分詞器,適用於英文
//Analyzer analyzer = new SmartChineseAnalyzer();//中文分詞
//Analyzer analyzer = new ComplexAnalyzer();//中文分詞
//Analyzer analyzer = new IKAnalyzer();//中文分詞
Analyzer analyzer = new IKAnalyzer();//中文分詞
//建立索引寫入配置
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
//建立索引寫入物件
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 刪除title中含有關鍵詞“Spark”的文件
long count = indexWriter.deleteDocuments(new Term("title", "Spark"));
// 除此之外IndexWriter還提供了以下方法:
// DeleteDocuments(Query query):根據Query條件來刪除單個或多個Document
// DeleteDocuments(Query[] queries):根據Query條件來刪除單個或多個Document
// DeleteDocuments(Term term):根據Term來刪除單個或多個Document
// DeleteDocuments(Term[] terms):根據Term來刪除單個或多個Document
// DeleteAll():刪除所有的Document
//使用IndexWriter進行Document刪除操作時,文件並不會立即被刪除,而是把這個刪除動作快取起來,當IndexWriter.Commit()或IndexWriter.Close()時,刪除操作才會被真正執行。
indexWriter.commit();
indexWriter.close();
System.out.println("刪除完成:" + count);
}
響應
刪除完成:1
更新文件
/**
* 測試更新
* 實際上就是刪除後新增一條
*
* @throws IOException
*/
@Test
public void updateDocumentTest() throws IOException {
//Analyzer analyzer = new StandardAnalyzer(); // 標準分詞器,適用於英文
//Analyzer analyzer = new SmartChineseAnalyzer();//中文分詞
//Analyzer analyzer = new ComplexAnalyzer();//中文分詞
//Analyzer analyzer = new IKAnalyzer();//中文分詞
Analyzer analyzer = new IKAnalyzer();//中文分詞
//建立索引寫入配置
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
//建立索引寫入物件
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
Document doc = new Document();
int id = 1;
doc.add(new IntPoint("id", id));
doc.add(new StringField("title", "Spark", Field.Store.YES));
doc.add(new TextField("content", "Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎", Field.Store.YES));
doc.add(new StoredField("id", id));
long count = indexWriter.updateDocument(new Term("id", "1"), doc);
System.out.println("更新文件:" + count);
indexWriter.close();
}
響應
更新文件:1
按詞條搜尋
/**
* 按詞條搜尋
* <p>
* TermQuery是最簡單、也是最常用的Query。TermQuery可以理解成為“詞條搜尋”,
* 在搜尋引擎中最基本的搜尋就是在索引中搜索某一詞條,而TermQuery就是用來完成這項工作的。
* 在Lucene中詞條是最基本的搜尋單位,從本質上來講一個詞條其實就是一個名/值對。
* 只不過這個“名”是欄位名,而“值”則表示欄位中所包含的某個關鍵字。
*
* @throws IOException
*/
@Test
public void termQueryTest() throws IOException {
String searchField = "title";
//這是一個條件查詢的api,用於新增條件
TermQuery query = new TermQuery(new Term(searchField, "Spark"));
//執行查詢,並列印查詢到的記錄數
executeQuery(query);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
多條件查詢
/**
* 多條件查詢
*
* BooleanQuery也是實際開發過程中經常使用的一種Query。
* 它其實是一個組合的Query,在使用時可以把各種Query物件新增進去並標明它們之間的邏輯關係。
* BooleanQuery本身來講是一個布林子句的容器,它提供了專門的API方法往其中新增子句,
* 並標明它們之間的關係,以下程式碼為BooleanQuery提供的用於新增子句的API介面:
*
* @throws IOException
*/
@Test
public void BooleanQueryTest() throws IOException {
String searchField1 = "title";
String searchField2 = "content";
Query query1 = new TermQuery(new Term(searchField1, "Spark"));
Query query2 = new TermQuery(new Term(searchField2, "Apache"));
BooleanQuery.Builder builder = new BooleanQuery.Builder();
// BooleanClause用於表示布林查詢子句關係的類,
// 包 括:
// BooleanClause.Occur.MUST,
// BooleanClause.Occur.MUST_NOT,
// BooleanClause.Occur.SHOULD。
// 必須包含,不能包含,可以包含三種.有以下6種組合:
//
// 1.MUST和MUST:取得連個查詢子句的交集。
// 2.MUST和MUST_NOT:表示查詢結果中不能包含MUST_NOT所對應得查詢子句的檢索結果。
// 3.SHOULD與MUST_NOT:連用時,功能同MUST和MUST_NOT。
// 4.SHOULD與MUST連用時,結果為MUST子句的檢索結果,但是SHOULD可影響排序。
// 5.SHOULD與SHOULD:表示“或”關係,最終檢索結果為所有檢索子句的並集。
// 6.MUST_NOT和MUST_NOT:無意義,檢索無結果。
builder.add(query1, BooleanClause.Occur.SHOULD);
builder.add(query2, BooleanClause.Occur.SHOULD);
BooleanQuery query = builder.build();
//執行查詢,並列印查詢到的記錄數
executeQuery(query);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
匹配字首
/**
* 匹配字首
* <p>
* PrefixQuery用於匹配其索引開始以指定的字串的文件。就是文件中存在xxx%
* <p>
*
* @throws IOException
*/
@Test
public void prefixQueryTest() throws IOException {
String searchField = "title";
Term term = new Term(searchField, "Spar");
Query query = new PrefixQuery(term);
//執行查詢,並列印查詢到的記錄數
executeQuery(query);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
短語搜尋
/**
* 短語搜尋
* <p>
* 所謂PhraseQuery,就是通過短語來檢索,比如我想查“big car”這個短語,
* 那麼如果待匹配的document的指定項裡包含了"big car"這個短語,
* 這個document就算匹配成功。可如果待匹配的句子裡包含的是“big black car”,
* 那麼就無法匹配成功了,如果也想讓這個匹配,就需要設定slop,
* 先給出slop的概念:slop是指兩個項的位置之間允許的最大間隔距離
*
* @throws IOException
*/
@Test
public void phraseQueryTest() throws IOException {
String searchField = "content";
String query1 = "apache";
String query2 = "spark";
PhraseQuery.Builder builder = new PhraseQuery.Builder();
builder.add(new Term(searchField, query1));
builder.add(new Term(searchField, query2));
builder.setSlop(0);
PhraseQuery phraseQuery = builder.build();
//執行查詢,並列印查詢到的記錄數
executeQuery(phraseQuery);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
相近詞語搜尋
/**
* 相近詞語搜尋
* <p>
* FuzzyQuery是一種模糊查詢,它可以簡單地識別兩個相近的詞語。
*
* @throws IOException
*/
@Test
public void fuzzyQueryTest() throws IOException {
String searchField = "content";
Term t = new Term(searchField, "大規模");
Query query = new FuzzyQuery(t);
//執行查詢,並列印查詢到的記錄數
executeQuery(query);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
萬用字元搜尋
/**
* 萬用字元搜尋
* <p>
* Lucene也提供了萬用字元的查詢,這就是WildcardQuery。
* 萬用字元“?”代表1個字元,而“*”則代表0至多個字元。
*
* @throws IOException
*/
@Test
public void wildcardQueryTest() throws IOException {
String searchField = "content";
Term term = new Term(searchField, "大*規模");
Query query = new WildcardQuery(term);
//執行查詢,並列印查詢到的記錄數
executeQuery(query);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
分詞查詢
/**
* 分詞查詢
*
* @throws IOException
* @throws ParseException
*/
@Test
public void queryParserTest() throws IOException, ParseException {
//Analyzer analyzer = new StandardAnalyzer(); // 標準分詞器,適用於英文
//Analyzer analyzer = new SmartChineseAnalyzer();//中文分詞
//Analyzer analyzer = new ComplexAnalyzer();//中文分詞
//Analyzer analyzer = new IKAnalyzer();//中文分詞
Analyzer analyzer = new IKAnalyzer();//中文分詞
String searchField = "content";
//指定搜尋欄位和分析器
QueryParser parser = new QueryParser(searchField, analyzer);
//使用者輸入內容
Query query = parser.parse("計算引擎");
//執行查詢,並列印查詢到的記錄數
executeQuery(query);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
多個 Field 分詞查詢
/**
* 多個 Field 分詞查詢
*
* @throws IOException
* @throws ParseException
*/
@Test
public void multiFieldQueryParserTest() throws IOException, ParseException {
//Analyzer analyzer = new StandardAnalyzer(); // 標準分詞器,適用於英文
//Analyzer analyzer = new SmartChineseAnalyzer();//中文分詞
//Analyzer analyzer = new ComplexAnalyzer();//中文分詞
//Analyzer analyzer = new IKAnalyzer();//中文分詞
Analyzer analyzer = new IKAnalyzer();//中文分詞
String[] filedStr = new String[]{"title", "content"};
//指定搜尋欄位和分析器
QueryParser queryParser = new MultiFieldQueryParser(filedStr, analyzer);
//使用者輸入內容
Query query = queryParser.parse("Spark");
//執行查詢,並列印查詢到的記錄數
executeQuery(query);
}
響應
總共查詢到1個文件
id:1
title:Spark
content:Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎!
中文分詞器
/**
* IKAnalyzer 中文分詞器
* SmartChineseAnalyzer smartcn分詞器 需要lucene依賴 且和lucene版本同步
*
* @throws IOException
*/
@Test
public void AnalyzerTest() throws IOException {
//Analyzer analyzer = new StandardAnalyzer(); // 標準分詞器,適用於英文
//Analyzer analyzer = new SmartChineseAnalyzer();//中文分詞
//Analyzer analyzer = new ComplexAnalyzer();//中文分詞
//Analyzer analyzer = new IKAnalyzer();//中文分詞
Analyzer analyzer = null;
String text = "Apache Spark 是專為大規模資料處理而設計的快速通用的計算引擎";
analyzer = new IKAnalyzer();//IKAnalyzer 中文分詞
printAnalyzerDoc(analyzer, text);
System.out.println();
analyzer = new ComplexAnalyzer();//MMSeg4j 中文分詞
printAnalyzerDoc(analyzer, text);
System.out.println();
analyzer = new SmartChineseAnalyzer();//Lucene 中文分詞器
printAnalyzerDoc(analyzer, text);
}
三種分詞響應
apache
spark
專為
大規模
規模
模數
資料處理
資料
處理
而設
設計
快速
通用
計算
引擎
apache
spark
是
專為
大規模
資料處理
而
設計
的
快速
通用
的
計算
引擎
apach
spark
是
專
為
大規模
資料
處理
而
設計
的
快速
通用
的
計算
引擎
高亮處理
/**
* 高亮處理
*
* @throws IOException
*/
@Test
public void HighlighterTest() throws IOException, ParseException, InvalidTokenOffsetsException {
//Analyzer analyzer = new StandardAnalyzer(); // 標準分詞器,適用於英文
//Analyzer analyzer = new SmartChineseAnalyzer();//中文分詞
//Analyzer analyzer = new ComplexAnalyzer();//中文分詞
//Analyzer analyzer = new IKAnalyzer();//中文分詞
Analyzer analyzer = new IKAnalyzer();//中文分詞
String searchField = "content";
String text = "Apache Spark 大規模資料處理";
//指定搜尋欄位和分析器
QueryParser parser = new QueryParser(searchField, analyzer);
//使用者輸入內容
Query query = parser.parse(text);
TopDocs topDocs = indexSearcher.search(query, 100);
// 關鍵字高亮顯示的html標籤,需要匯入lucene-highlighter-xxx.jar
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
//取得對應的文件物件
Document document = indexSearcher.doc(scoreDoc.doc);
// 內容增加高亮顯示
TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(document.get("content")));
String content = highlighter.getBestFragment(tokenStream, document.get("content"));
System.out.println(content);
}
}
響應
<span style='color:red'>Apache</span> <span style='color:red'>Spark</span> 是專為<span style='color:red'>大規模資料處理</span>而設計的快速通用的計算引擎!
程式碼我已放到 Github ,匯入spring-boot-lucene-demo
專案
Lucene是apache軟體基金會4 jakarta專案組的一個子專案,是一個開放原始碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文字分析引擎(英文與德文兩種西方語言)。Lucene的目的是為軟體開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此為基礎建立起完整的全文檢索引擎
全文檢索概述
比如,我們一個資料夾中,或者一個磁碟中有很多的檔案,記事本、world、Excel、pdf,我們想根據其中的關鍵詞搜尋包含的檔案。例如,我們輸入Lucene,所有內容含有Lucene的檔案就會被檢查出來。這就是所謂的全文檢索。
因此,很容易的我們想到,應該建立一個關鍵字與檔案的相關對映,盜用ppt中的一張圖,很明白的解釋了這種對映如何實現。
倒排索引
有了這種對映關係,我們就來看看Lucene的架構設計。 下面是Lucene的資料必出現的一張圖,但也是其精髓的概括。
我們可以看到,Lucene的使用主要體現在兩個步驟:
1 建立索引,通過IndexWriter對不同的檔案進行索引的建立,並將其儲存在索引相關檔案儲存的位置中。
2 通過索引查尋關鍵字相關文件。
在Lucene中,就是使用這種“倒排索引”的技術,來實現相關對映。
Lucene數學模型
文件、域、詞元
文件是Lucene搜尋和索引的原子單位,文件為包含一個或者多個域的容器,而域則是依次包含“真正的”被搜尋的內容,域值通過分詞技術處理,得到多個詞元。
For Example,一篇小說(鬥破蒼穹)資訊可以稱為一個文件,小說資訊又包含多個域,例如:標題(鬥破蒼穹)、作者、簡介、最後更新時間等等,對標題這個域採用分詞技術又可以得到一個或者多個詞元(鬥、破、蒼、穹)。
Lucene檔案結構
層次結構
index 一個索引存放在一個目錄中
segment 一個索引中可以有多個段,段與段之間是獨立的,新增新的文件可能產生新段,不同的段可以合併成一個新段
document 文件是建立索引的基本單位,不同的文件儲存在不同的段中,一個段可以包含多個文件
field 域,一個文件包含不同型別的資訊,可以拆分開索引
term 詞,索引的最小單位,是經過詞法分析和語言處理後的資料。
正向資訊
按照層次依次儲存了從索引到詞的包含關係:index–>segment–>document–>field–>term。
反向資訊
反向資訊儲存了詞典的倒排表對映:term–>document
IndexWriter lucene中最重要的的類之一,它主要是用來將文件加入索引,同時控制索引過程中的一些引數使用。
Analyzer 分析器,主要用於分析搜尋引擎遇到的各種文字。常用的有StandardAnalyzer分析器,StopAnalyzer分析器,WhitespaceAnalyzer分析器等。
Directory 索引存放的位置;lucene提供了兩種索引存放的位置,一種是磁碟,一種是記憶體。一般情況將索引放在磁碟上;相應地lucene提供了FSDirectory和RAMDirectory兩個類。
Document 文件;Document相當於一個要進行索引的單元,任何可以想要被索引的檔案都必須轉化為Document物件才能進行索引。
Field 欄位。
IndexSearcher 是lucene中最基本的檢索工具,所有的檢索都會用到IndexSearcher工具;
Query 查詢,lucene中支援模糊查詢,語義查詢,短語查詢,組合查詢等等,如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些類。
QueryParser 是一個解析使用者輸入的工具,可以通過掃描使用者輸入的字串,生成Query物件。
Hits 在搜尋完成之後,需要把搜尋結果返回並顯示給使用者,只有這樣才算是完成搜尋的目的。在lucene中,搜尋的結果的集合是用Hits類的例項來表示的。