Spring Boot 中使用 Java API 調用 lucene

Lucene是apache軟件基金會4 jakarta項目組的一個子項目,是一個開放源代碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此爲基礎創建起完整的全文檢索引擎html

全文檢索概述

好比,咱們一個文件夾中,或者一個磁盤中有不少的文件,記事本、world、Excel、pdf,咱們想根據其中的關鍵詞搜索包含的文件。例如,咱們輸入Lucene,全部內容含有Lucene的文件就會被檢查出來。這就是所謂的全文檢索。java

所以,很容易的咱們想到,應該創建一個關鍵字與文件的相關映射,盜用ppt中的一張圖,很明白的解釋了這種映射如何實現。git

倒排索引github

倒排索引

有了這種映射關係,咱們就來看看Lucene的架構設計。 下面是Lucene的資料必出現的一張圖,但也是其精髓的歸納。spring

倒排

咱們能夠看到,Lucene的使用主要體如今兩個步驟:apache

1 建立索引,經過IndexWriter對不一樣的文件進行索引的建立,並將其保存在索引相關文件存儲的位置中。api

2 經過索引查尋關鍵字相關文檔。緩存

在Lucene中,就是使用這種「倒排索引」的技術,來實現相關映射。bash

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類的實例來表示的。

測試用例

Github 代碼

代碼我已放到 Github ,導入spring-boot-lucene-demo 項目

github github.com/souyunku/sp…

添加依賴

<!--對分詞索引查詢解析-->
<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 項目

github github.com/souyunku/sp…

Contact

  • 做者:鵬磊
  • 出處:www.ymq.io
  • Email:admin@souyunku.com
  • 版權歸做者全部,轉載請註明出處
  • Wechat:關注公衆號,搜雲庫,專一於開發技術的研究與知識分享

關注公衆號-搜雲庫
搜雲庫
相關文章
相關標籤/搜索